/*
* Copyright (C) 2012 - 2014 Brandon Tate, bossturbo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.angrydroids.epub3reader;
import org.json.JSONException;
import org.json.JSONObject;
import com.blahti.drag.DragController;
import com.blahti.drag.DragController.DragBehavior;
import com.blahti.drag.DragLayer;
import com.blahti.drag.DragListener;
import com.blahti.drag.DragSource;
import com.blahti.drag.MyAbsoluteLayout;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.Toast;
@SuppressLint("DefaultLocale")
public class TextSelectionSupport extends SplitPanel implements
TextSelectionControlListener, OnTouchListener, OnLongClickListener,
DragListener {
public interface SelectionListener {
void startSelection();
void selectionChanged(String text);
void endSelection();
}
private enum HandleType {
START, END, UNKNOWN
}
private static final String TAG = "SelectionSupport";
private static final float CENTERING_SHORTER_MARGIN_RATIO = 12.0f / 48.0f;
private static final int JACK_UP_PADDING = 2;
private static final int SCROLLING_THRESHOLD = 10;
private Activity mActivity;
private WebView mWebView;
private SelectionListener mSelectionListener;
private DragLayer mSelectionDragLayer;
private DragController mDragController;
private ImageView mStartSelectionHandle;
private ImageView mEndSelectionHandle;
private Rect mSelectionBounds = null;
private final Rect mSelectionBoundsTemp = new Rect();
private TextSelectionController mSelectionController = null;
private int mContentWidth = 0;
private HandleType mLastTouchedSelectionHandle = HandleType.UNKNOWN;
private boolean mScrolling = false;
private float mScrollDiffY = 0;
private float mLastTouchY = 0;
private float mScrollDiffX = 0;
private float mLastTouchX = 0;
private float mScale = 1.0f;
private Runnable mStartSelectionModeHandler = new Runnable() {
public void run() {
if (mSelectionBounds != null) {
mWebView.addView(mSelectionDragLayer);
drawSelectionHandles();
final int contentHeight = (int) Math
.ceil(getDensityDependentValue(
mWebView.getContentHeight(), mActivity));
final int contentWidth = mWebView.getWidth();
ViewGroup.LayoutParams layerParams = mSelectionDragLayer
.getLayoutParams();
layerParams.height = contentHeight;
layerParams.width = Math.max(contentWidth, mContentWidth);
mSelectionDragLayer.setLayoutParams(layerParams);
if (mSelectionListener != null) {
mSelectionListener.startSelection();
}
}
}
};
private Runnable endSelectionModeHandler = new Runnable() {
public void run() {
mWebView.removeView(mSelectionDragLayer);
mSelectionBounds = null;
mLastTouchedSelectionHandle = HandleType.UNKNOWN;
mWebView.loadUrl("javascript: android.selection.clearSelection();");
if (mSelectionListener != null) {
mSelectionListener.endSelection();
}
}
};
private TextSelectionSupport(Activity activity, WebView webview) {
mActivity = activity;
mWebView = webview;
}
public static TextSelectionSupport support(Activity activity,
WebView webview) {
final TextSelectionSupport selectionSupport = new TextSelectionSupport(
activity, webview);
selectionSupport.setup();
return selectionSupport;
}
public void onScaleChanged(float oldScale, float newScale) {
mScale = newScale;
}
public void setSelectionListener(SelectionListener listener) {
mSelectionListener = listener;
}
//
// Interfaces of TextSelectionControlListener
//
@Override
public void jsError(String error) {
Log.e(TAG, "JSError: " + error);
}
@Override
public void jsLog(String message) {
Log.d(TAG, "JSLog: " + message);
}
@Override
public void startSelectionMode() {
mActivity.runOnUiThread(mStartSelectionModeHandler);
}
@Override
public void endSelectionMode() {
mActivity.runOnUiThread(endSelectionModeHandler);
}
@Override
public void setContentWidth(float contentWidth) {
mContentWidth = (int) getDensityDependentValue(contentWidth, mActivity);
}
@Override
public void selectionChanged(String range, String text,
String handleBounds, boolean isReallyChanged) {
final Context ctx = mActivity;
try {
final JSONObject selectionBoundsObject = new JSONObject(
handleBounds);
final float scale = getDensityIndependentValue(mScale, ctx);
Rect rect = mSelectionBoundsTemp;
rect.left = (int) (getDensityDependentValue(
selectionBoundsObject.getInt("left"), ctx) * scale);
rect.top = (int) (getDensityDependentValue(
selectionBoundsObject.getInt("top"), ctx) * scale);
rect.right = (int) (getDensityDependentValue(
selectionBoundsObject.getInt("right"), ctx) * scale);
rect.bottom = (int) (getDensityDependentValue(
selectionBoundsObject.getInt("bottom"), ctx) * scale);
mSelectionBounds = rect;
if (!isInSelectionMode()) {
startSelectionMode();
}
drawSelectionHandles();
if (mSelectionListener != null && isReallyChanged) {
mSelectionListener.selectionChanged(text);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
//
// Interface of OnTouchListener
//
@Override
public boolean onTouch(View v, MotionEvent event) {
final Context ctx = mActivity;
float xPoint = getDensityIndependentValue(event.getX(), ctx)
/ getDensityIndependentValue(mScale, ctx);
float yPoint = getDensityIndependentValue(event.getY(), ctx)
/ getDensityIndependentValue(mScale, ctx);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
final String startTouchUrl = String.format(
"javascript:android.selection.startTouch(%f, %f);", xPoint,
yPoint);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
mWebView.loadUrl(startTouchUrl);
break;
case MotionEvent.ACTION_UP:
if (!mScrolling) {
endSelectionMode();
//
// Fixes 4.4 double selection
// See:
// http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return false;
}
}
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
//
// Fixes 4.4 double selection
// See:
// http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return true;
}
break;
case MotionEvent.ACTION_MOVE:
mScrollDiffX += (xPoint - mLastTouchX);
mScrollDiffY += (yPoint - mLastTouchY);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
if (Math.abs(mScrollDiffX) > SCROLLING_THRESHOLD
|| Math.abs(mScrollDiffY) > SCROLLING_THRESHOLD) {
mScrolling = true;
}
break;
}
return false;
}
//
// Interface of OnLongClickListener
//
@Override
public boolean onLongClick(View v) {
if (!isInSelectionMode()) {
mWebView.loadUrl("javascript:android.selection.longTouch();");
mScrolling = true;
}
Message msg = new Message();
msg.setTarget(new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String url = msg.getData().getString("url");
if (url != null)
navigator.setNote(url, index);
}
});
mWebView.requestFocusNodeHref(msg);
return true;
}
//
// Interface of DragListener
//
@Override
public void onDragStart(DragSource source, Object info,
DragBehavior dragBehavior) {
}
@Override
public void onDragEnd() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams) mStartSelectionHandle
.getLayoutParams();
MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams) mEndSelectionHandle
.getLayoutParams();
final Context ctx = mActivity;
final float scale = getDensityIndependentValue(mScale, ctx);
float startX = startHandleParams.x - mWebView.getScrollX()
+ mStartSelectionHandle.getWidth()
* (1 - CENTERING_SHORTER_MARGIN_RATIO);
float startY = startHandleParams.y - mWebView.getScrollY()
- JACK_UP_PADDING;
float endX = endHandleParams.x - mWebView.getScrollX()
+ mEndSelectionHandle.getWidth()
* CENTERING_SHORTER_MARGIN_RATIO;
float endY = endHandleParams.y - mWebView.getScrollY()
- JACK_UP_PADDING;
startX = getDensityIndependentValue(startX, ctx) / scale;
startY = getDensityIndependentValue(startY, ctx) / scale;
endX = getDensityIndependentValue(endX, ctx) / scale;
endY = getDensityIndependentValue(endY, ctx) / scale;
if (mLastTouchedSelectionHandle == HandleType.START
&& startX > 0 && startY > 0) {
String saveStartString = String
.format("javascript: android.selection.setStartPos(%f, %f);",
startX, startY);
mWebView.loadUrl(saveStartString);
} else if (mLastTouchedSelectionHandle == HandleType.END
&& endX > 0 && endY > 0) {
String saveEndString = String.format(
"javascript: android.selection.setEndPos(%f, %f);",
endX, endY);
mWebView.loadUrl(saveEndString);
} else {
mWebView.loadUrl("javascript: android.selection.restoreStartEndPos();");
}
}
});
}
@SuppressLint("SetJavaScriptEnabled")
private void setup() {
mScale = mActivity.getResources().getDisplayMetrics().density;
mWebView.setOnLongClickListener(this);
mWebView.setOnTouchListener(this);
mSelectionController = new TextSelectionController(this);
mWebView.addJavascriptInterface(mSelectionController,
TextSelectionController.INTERFACE_NAME);
createSelectionLayer(mActivity);
}
private void createSelectionLayer(Context context) {
final LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSelectionDragLayer = (DragLayer) inflater.inflate(
R.layout.selection_drag_layer, null);
mDragController = new DragController(context);
mDragController.setDragListener(this);
mDragController.addDropTarget(mSelectionDragLayer);
mSelectionDragLayer.setDragController(mDragController);
mStartSelectionHandle = (ImageView) mSelectionDragLayer
.findViewById(R.id.startHandle);
mStartSelectionHandle.setTag(HandleType.START);
mEndSelectionHandle = (ImageView) mSelectionDragLayer
.findViewById(R.id.endHandle);
mEndSelectionHandle.setTag(HandleType.END);
final OnTouchListener handleTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean handledHere = false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
handledHere = startDrag(v);
mLastTouchedSelectionHandle = (HandleType) v.getTag();
}
return handledHere;
}
};
mStartSelectionHandle.setOnTouchListener(handleTouchListener);
mEndSelectionHandle.setOnTouchListener(handleTouchListener);
}
private void drawSelectionHandles() {
mActivity.runOnUiThread(drawSelectionHandlesHandler);
}
private Runnable drawSelectionHandlesHandler = new Runnable() {
public void run() {
MyAbsoluteLayout.LayoutParams startParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams) mStartSelectionHandle
.getLayoutParams();
final int startWidth = mStartSelectionHandle.getDrawable()
.getIntrinsicWidth();
startParams.x = (int) (mSelectionBounds.left - startWidth
* (1.0f - CENTERING_SHORTER_MARGIN_RATIO));
startParams.y = (int) (mSelectionBounds.top);
final int startMinLeft = -(int) (startWidth * (1 - CENTERING_SHORTER_MARGIN_RATIO));
startParams.x = (startParams.x < startMinLeft) ? startMinLeft
: startParams.x;
startParams.y = (startParams.y < 0) ? 0 : startParams.y;
mStartSelectionHandle.setLayoutParams(startParams);
MyAbsoluteLayout.LayoutParams endParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams) mEndSelectionHandle
.getLayoutParams();
final int endWidth = mEndSelectionHandle.getDrawable()
.getIntrinsicWidth();
endParams.x = (int) (mSelectionBounds.right - endWidth
* CENTERING_SHORTER_MARGIN_RATIO);
endParams.y = (int) (mSelectionBounds.bottom);
final int endMinLeft = -(int) (endWidth * (1 - CENTERING_SHORTER_MARGIN_RATIO));
endParams.x = (endParams.x < endMinLeft) ? endMinLeft : endParams.x;
endParams.y = (endParams.y < 0) ? 0 : endParams.y;
mEndSelectionHandle.setLayoutParams(endParams);
}
};
private boolean isInSelectionMode() {
return this.mSelectionDragLayer.getParent() != null;
}
private boolean startDrag(View v) {
Object dragInfo = v;
mDragController.startDrag(v, mSelectionDragLayer, dragInfo,
DragBehavior.MOVE);
return true;
}
private float getDensityDependentValue(float val, Context ctx) {
Display display = ((WindowManager) ctx
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val * (metrics.densityDpi / 160f);
}
private float getDensityIndependentValue(float val, Context ctx) {
Display display = ((WindowManager) ctx
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val / (metrics.densityDpi / 160f);
}
}