X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff..269195b3729c1bdc22e9053ee4ebca667ea8549d:/Android/webViewMarker/src/main/java/com/bossturban/webviewmarker/TextSelectionSupport.java diff --git a/Android/webViewMarker/src/main/java/com/bossturban/webviewmarker/TextSelectionSupport.java b/Android/webViewMarker/src/main/java/com/bossturban/webviewmarker/TextSelectionSupport.java new file mode 100755 index 0000000..456f7ef --- /dev/null +++ b/Android/webViewMarker/src/main/java/com/bossturban/webviewmarker/TextSelectionSupport.java @@ -0,0 +1,367 @@ +/* + * 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 com.bossturban.webviewmarker; + +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 com.bossturban.webviewmarker.R; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +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 java.util.Locale; + +@SuppressLint("DefaultLocale") +public class TextSelectionSupport 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: + // Essential to add Locale.US parameter to String.format, else does not work on systems + // with default locale different, with other floating point notations, e.g. comma instead + // of decimal point. + final String startTouchUrl = String.format(Locale.US, "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 && isInSelectionMode()) { + 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; + } + 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(Locale.US, "javascript: android.selection.setStartPos(%f, %f);", startX, startY); + mWebView.loadUrl(saveStartString); + } + else if (mLastTouchedSelectionHandle == HandleType.END && endX > 0 && endY > 0){ + String saveEndString = String.format(Locale.US, "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); + final WebSettings settings = mWebView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setJavaScriptCanOpenWindowsAutomatically(true); + 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); + } +} \ No newline at end of file