package org.ebookdroid.core; import org.ebookdroid.common.settings.AppSettings; import org.ebookdroid.common.settings.books.BookSettings; import org.ebookdroid.common.settings.types.DocumentViewMode; import org.ebookdroid.common.settings.types.PageAlign; import org.ebookdroid.common.settings.types.PageType; import org.ebookdroid.core.models.DocumentModel; import org.ebookdroid.core.models.DocumentModel.PageIterator; import org.ebookdroid.ui.viewer.IView; import org.ebookdroid.ui.viewer.IViewController; import android.graphics.Color; import android.graphics.PointF; import android.graphics.RectF; import java.util.concurrent.ConcurrentLinkedQueue; public class ViewState { private static final ConcurrentLinkedQueue<ViewState> states = new ConcurrentLinkedQueue<ViewState>(); private State state; public AppSettings app; public BookSettings book; public IViewController ctrl; public DocumentModel model; public boolean nightMode; public boolean positiveImagesInNightMode; public boolean tint; public int tintColor; public float zoom; public PageAlign pageAlign; public PagePaint paint; public final Pages pages; public RectF viewRect; public PointF viewBase; public static ViewState get(final IViewController dc) { return get().init(dc, dc.getBase().getZoomModel().getZoom()); } public static ViewState get(final IViewController dc, final float zoom) { return get().init(dc, zoom); } private static ViewState get() { ViewState state = null; while (true) { state = states.poll(); if (state == null) { state = new ViewState(); return state; } if (state.use()) { return state; } } } private ViewState() { pages = new Pages(); state = State.RELEASED; use(); } private synchronized boolean use() { if (state == State.RELEASED || state == State.RELEASED_AFTER_DRAW) { state = State.USED; return true; } return false; } public synchronized void release() { switch (state) { case USED: state = State.RELEASED; states.add(this); return; case QUEUED: case RELEASED: case RELEASED_AFTER_DRAW: return; } } public synchronized void addedToDrawQueue() { switch (state) { case USED: state = State.QUEUED; return; case QUEUED: case RELEASED: case RELEASED_AFTER_DRAW: return; } } public synchronized void releaseAfterDraw() { switch (state) { case QUEUED: state = State.RELEASED_AFTER_DRAW; states.add(this); return; case USED: case RELEASED: case RELEASED_AFTER_DRAW: return; } } private ViewState init(final IViewController dc, final float zoom) { this.app = AppSettings.current(); this.book = dc.getBase().getBookSettings(); this.ctrl = dc; this.model = dc.getBase().getDocumentModel(); this.nightMode = book != null ? book.nightMode : app.nightMode; this.positiveImagesInNightMode = book != null ? book.positiveImagesInNightMode : app.positiveImagesInNightMode; this.tint = book != null ? book.tint : app.tint; this.tintColor = book != null ? book.tintColor : app.tintColor; this.pageAlign = DocumentViewMode.getPageAlign(book); this.paint = this.tint ? PagePaint.TintedDay(this.tintColor) : (this.nightMode ? PagePaint.Night() : PagePaint.Day()); this.paint.bitmapPaint.setFilterBitmap(false); this.zoom = zoom; this.viewRect = new RectF(ctrl.getView().getViewRect()); this.viewBase = ctrl.getView().getBase(viewRect); this.pages.update(); return this; } public void update() { this.zoom = ctrl.getBase().getZoomModel().getZoom(); this.viewRect = new RectF(ctrl.getView().getViewRect()); this.viewBase = ctrl.getView().getBase(viewRect); this.pages.update(ctrl.getFirstVisiblePage(), ctrl.getLastVisiblePage()); } public ViewState update(final int firstVisiblePage, final int lastVisiblePage) { this.pages.update(firstVisiblePage, lastVisiblePage); return this; } public RectF getBounds(final Page page) { return page.getBounds(zoom); } public void getBounds(final Page page, final RectF target) { page.getBounds(zoom, target); } public final boolean isPageKeptInMemory(final Page page) { return pages.firstCached <= page.index.viewIndex && page.index.viewIndex <= pages.lastCached; } public final boolean isPageVisible(final Page page) { return pages.firstVisible <= page.index.viewIndex && page.index.viewIndex <= pages.lastVisible; } public final boolean isNodeKeptInMemory(final PageTreeNode node, final RectF pageBounds) { if (this.zoom < 1.5) { return this.isPageKeptInMemory(node.page) || this.isPageVisible(node.page); } if (this.zoom < 2.5) { return this.isPageKeptInMemory(node.page) || this.isNodeVisible(node, pageBounds); } return this.isNodeVisible(node, pageBounds); } public final boolean isNodeVisible(final PageTreeNode node, final RectF pageBounds) { final RectF tr = node.getTargetRect(pageBounds); return isNodeVisible(tr); } public final boolean isNodeVisible(final RectF tr) { return RectF.intersects(viewRect, tr); } public final PointF getPositionOnPage(final Page page) { final PointF pos = new PointF(); final IView view = ctrl.getView(); if (view != null) { final int left = view.getScrollX(); final int top = view.getScrollY(); final RectF cpBounds = getBounds(page); pos.x = (left - cpBounds.left) / cpBounds.width(); pos.y = (top - cpBounds.top) / cpBounds.height(); } return pos; } public final PointF getPositionOnPage(final Page page, final int x, final int y) { final PointF pos = new PointF(); final IView view = ctrl.getView(); if (view != null) { System.out.println("ViewState.getPositionOnPage(" + x + "," + y + "," + view.getScrollX() + "," + view.getScrollY() + ")"); final int left = x + view.getScrollX(); final int top = y + view.getScrollY(); final RectF cpBounds = getBounds(page); pos.x = (left - cpBounds.left) / cpBounds.width(); pos.y = (top - cpBounds.top) / cpBounds.height(); final RectF cropping = page.getCropping(); if (cropping != null) { pos.x *= cropping.width(); pos.x += cropping.left; pos.y *= cropping.height(); pos.y += cropping.top; } else { if (page.type == PageType.RIGHT_PAGE) { pos.x += 0.5; } } } return pos; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); pages.toString(buf).append(" ").append("zoom: ").append(zoom); return buf.toString(); } public class Pages extends ViewPages { private Pages() { } public void update() { update(ctrl.getFirstVisiblePage(), ctrl.getLastVisiblePage()); } public void update(final int firstVisible, final int lastVisible) { this.firstVisible = firstVisible; this.lastVisible = lastVisible; if (model != null) { this.currentIndex = ctrl.calculateCurrentPage(ViewState.this, firstVisible, lastVisible); final int inMemory = (int) Math.ceil(app.pagesInMemory / 2.0); this.firstCached = Math.max(0, this.currentIndex - inMemory); this.lastCached = Math.min(this.currentIndex + inMemory, model.getPageCount()); } else { this.currentIndex = firstVisible; this.firstCached = firstVisible; this.lastCached = lastVisible; } } public PageIterator getVisiblePages() { return firstVisible != -1 ? model.getPages(firstVisible, lastVisible + 1) : model.getPages(0); } public Page getCurrentPage() { return model.getPageObject(currentIndex); } } public static enum State { USED, QUEUED, RELEASED, RELEASED_AFTER_DRAW; } }