/*
* Copyright (C) 2010 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.android.fbreader;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;
import org.geometerplus.zlibrary.core.application.ZLApplication;
import org.geometerplus.zlibrary.core.util.ZLColor;
import org.geometerplus.zlibrary.core.view.ZLView;
import org.geometerplus.zlibrary.text.view.ZLTextView;
import org.geometerplus.zlibrary.ui.android.R;
import org.geometerplus.zlibrary.ui.android.library.ZLAndroidApplication;
import org.geometerplus.zlibrary.ui.android.view.ZLAndroidWidget;
public class SynchronousView extends View {
private EPDView myEPDView;
private ZLAndroidWidget myWidget;
private int myMinimumVelocity;
private VelocityTracker myVelocityTracker;
private Scroller myScroller;
private float myLastScrollX;
private float myLastScrollY;
private int myScrollX;
private int myScrollY;
private boolean myInvalidScroll = true;
private int myScrollPage;
private float myStartScrollX;
private float myStartScrollY;
private boolean myPendingClick;
public SynchronousView(Context context) {
super(context);
init();
}
public SynchronousView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SynchronousView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private final void init() {
setDrawingCacheEnabled(false);
myScroller = new Scroller(getContext());
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
myMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
}
public void setEPDView(EPDView view) {
myEPDView = view;
myWidget = (ZLAndroidWidget) view.getActivity().findViewById(R.id.main_view_epd);
}
public void invalidateScroll() {
myInvalidScroll = true;
}
public void resetScroll() {
if (myInvalidScroll) {
switch (ZLAndroidApplication.Instance().RotationFlag) {
case ZLAndroidApplication.ROTATE_0:
myScrollX = myScrollY = 0;
break;
case ZLAndroidApplication.ROTATE_90:
myScrollX = Integer.MAX_VALUE;
myScrollY = 0;
break;
case ZLAndroidApplication.ROTATE_180:
myScrollX = Integer.MAX_VALUE;
myScrollY = Integer.MAX_VALUE;
break;
case ZLAndroidApplication.ROTATE_270:
myScrollX = 0;
myScrollY = Integer.MAX_VALUE;
break;
}
}
myInvalidScroll = false;
}
private void normalizeScroll() {
myScrollX = Math.max(0, Math.min(myScrollX, myWidget.getWidth() - getClientWidth()));
myScrollY = Math.max(0, Math.min(myScrollY, myWidget.getHeight() - getClientHeight()));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (myWidget == null) {
return;
}
normalizeScroll();
final ZLView view = ZLApplication.Instance().getCurrentView();
if (view instanceof ZLTextView) {
final ZLColor color = ((ZLTextView) view).getBackgroundColor();
//canvas.drawColor(Color.rgb(255-color.Red, 255-color.Green, 255-color.Blue));
canvas.drawColor(Color.rgb(color.Red, color.Green, color.Blue));
}
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.clipRect(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(),
Region.Op.REPLACE);
final Bitmap bmp = myWidget.getBitmap();
if (bmp != null && !bmp.isRecycled()) {
canvas.drawBitmap(bmp,
getPaddingLeft() - myScrollX,
getPaddingTop() - myScrollY,
null
);
}
canvas.restore();
}
private int getClientHeight() {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
private int getClientWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
private int getBitmapX(float viewX) {
final int value = (int) (viewX - getPaddingLeft() + myScrollX);
return Math.max(0, Math.min(value, myWidget.getWidth()));
}
private int getBitmapY(float viewY) {
final int value = (int) (viewY - getPaddingTop() + myScrollY);
return Math.max(0, Math.min(value, myWidget.getHeight()));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
return false;
}
if (myWidget == null || (myWidget.getHeight() <= getClientHeight()
&& myWidget.getWidth() <= getClientWidth())) {
myScrollY = 0;
myScrollX = 0;
return false;
}
normalizeScroll();
if (myVelocityTracker == null) {
myVelocityTracker = VelocityTracker.obtain();
}
myVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
final int angle = ZLAndroidApplication.Instance().RotationFlag;
final int widthDiff = myWidget.getWidth() - getClientWidth();
final int heightDiff = myWidget.getHeight() - getClientHeight();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!myScroller.isFinished()) {
myScroller.abortAnimation();
}
myStartScrollX = x;
myStartScrollY = y;
myLastScrollY = y;
myLastScrollX = x;
myScrollPage = 0;
if (angle == ZLAndroidApplication.ROTATE_0 || angle == ZLAndroidApplication.ROTATE_180) {
if (myScrollY == 0) {
myScrollPage = -1;
} else if (myScrollY == heightDiff) {
myScrollPage = 1;
}
} else {
if (myScrollX == widthDiff) {
myScrollPage = -1;
} else if (myScrollX == 0) {
myScrollPage = 1;
}
}
myPendingClick = true;
break;
case MotionEvent.ACTION_MOVE:
if (myPendingClick) {
final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
if (Math.abs(myStartScrollX - x) > slop || Math.abs(myStartScrollY - y) > slop) {
myPendingClick = false;
}
}
if (!myPendingClick) {
myScrollX += (int) (myLastScrollX - x);
myScrollY += (int) (myLastScrollY - y);
myLastScrollX = x;
myLastScrollY = y;
}
break;
case MotionEvent.ACTION_UP:
if (myPendingClick) {
if (x > getPaddingLeft() && x < getWidth() - getPaddingRight()
&& y > getPaddingTop() && y < getHeight() - getPaddingBottom()) {
int startX = -1, startY = -1, stopX = -1, stopY = -1;
switch (angle) {
case ZLAndroidApplication.ROTATE_0:
stopX = getBitmapX(x);
stopY = getBitmapY(y);
startX = getBitmapX(myStartScrollX);
startY = getBitmapY(myStartScrollY);
break;
case ZLAndroidApplication.ROTATE_90:
stopX = getBitmapY(y);
stopY = myWidget.getWidth() - getBitmapX(x);
startX = getBitmapY(myStartScrollY);
startY = myWidget.getWidth() - getBitmapX(myStartScrollX);
break;
case ZLAndroidApplication.ROTATE_180:
stopX = myWidget.getWidth() - getBitmapX(x);
stopY = myWidget.getHeight() - getBitmapY(y);
startX = myWidget.getWidth() - getBitmapX(myStartScrollX);
startY = myWidget.getHeight() - getBitmapY(myStartScrollY);
break;
case ZLAndroidApplication.ROTATE_270:
stopX = myWidget.getHeight() - getBitmapY(y);
stopY = getBitmapX(x);
startX = myWidget.getHeight() - getBitmapY(myStartScrollY);
startY = getBitmapX(myStartScrollX);
break;
}
if (startX >= 0 && startY >= 0 && stopX >= 0 && stopY >= 0) {
final ZLView view = ZLApplication.Instance().getCurrentView();
view.onFingerPress(startX, startY);
view.onFingerMove(stopX, stopY);
view.onFingerRelease(stopX, stopY);
ZLApplication.Instance().repaintView();
}
}
} else {
myVelocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) myVelocityTracker.getXVelocity();
int velocityY = (int) myVelocityTracker.getYVelocity();
if (Math.abs(velocityX) <= myMinimumVelocity) {
velocityX = 0;
}
if (Math.abs(velocityY) <= myMinimumVelocity) {
velocityY = 0;
}
final boolean tryScrollX = myScrollPage * velocityX > 0
&& Math.abs(myStartScrollX - x) > getWidth() / 4
&& Math.abs(myStartScrollY - y) < getHeight() / 3;
final boolean tryScrollY = myScrollPage * velocityY < 0
&& Math.abs(myStartScrollX - x) < getWidth() / 4
&& Math.abs(myStartScrollY - y) > getHeight() / 3;
if ((angle == ZLAndroidApplication.ROTATE_90 || angle == ZLAndroidApplication.ROTATE_270) ?
tryScrollX : tryScrollY) {
myInvalidScroll = true;
myEPDView.scrollPage((angle == ZLAndroidApplication.ROTATE_0
|| angle == ZLAndroidApplication.ROTATE_90) ?
(myScrollPage > 0) : (myScrollPage < 0)
);
} else {
myScroller.fling(myScrollX, myScrollY, -velocityX, -velocityY,
0, widthDiff, 0, heightDiff);
}
}
if (myVelocityTracker != null) {
myVelocityTracker.recycle();
myVelocityTracker = null;
}
break;
}
invalidate();
return true;
}
@Override
public void computeScroll() {
if (myScroller.computeScrollOffset()) {
myScrollX = myScroller.getCurrX();
myScrollY = myScroller.getCurrY();
postInvalidate();
}
}
@Override
protected int computeVerticalScrollRange() {
return (myWidget == null) ? getHeight() : myWidget.getHeight();
}
@Override
protected int computeVerticalScrollOffset() {
return myScrollY;
}
@Override
protected int computeHorizontalScrollRange() {
return (myWidget == null) ? getWidth() : myWidget.getWidth();
}
@Override
protected int computeHorizontalScrollOffset() {
return myScrollX;
}
private float computeFadingEdgeStrength(int offset, int length) {
if (offset < length) {
return offset / (float) length;
}
return 1.0f;
}
@Override
protected float getLeftFadingEdgeStrength() {
if (myWidget == null || myWidget.getWidth() <= getClientWidth()) {
return 0.0f;
}
return computeFadingEdgeStrength(myScrollX, getHorizontalFadingEdgeLength());
}
@Override
protected float getRightFadingEdgeStrength() {
if (myWidget == null || myWidget.getWidth() <= getClientWidth()) {
return 0.0f;
}
return computeFadingEdgeStrength(myWidget.getWidth() - myScrollX - getClientWidth(),
getHorizontalFadingEdgeLength());
}
@Override
protected float getTopFadingEdgeStrength() {
if (myWidget == null || myWidget.getHeight() <= getClientHeight()) {
return 0.0f;
}
return computeFadingEdgeStrength(myScrollY, getVerticalFadingEdgeLength());
}
@Override
protected float getBottomFadingEdgeStrength() {
if (myWidget == null || myWidget.getHeight() <= getClientHeight()) {
return 0.0f;
}
return computeFadingEdgeStrength(myWidget.getHeight() - myScrollY - getClientHeight(),
getVerticalFadingEdgeLength());
}
}