michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import org.mozilla.gecko.gfx.ImmutableViewportMetrics; michael@0: import org.mozilla.gecko.gfx.LayerView; michael@0: michael@0: import org.json.JSONObject; michael@0: michael@0: import android.content.Context; michael@0: import android.content.res.TypedArray; michael@0: import android.graphics.PointF; michael@0: import android.util.AttributeSet; michael@0: import android.util.Log; michael@0: import android.view.MotionEvent; michael@0: import android.view.View; michael@0: import android.widget.ImageView; michael@0: import android.widget.RelativeLayout; michael@0: michael@0: class TextSelectionHandle extends ImageView implements View.OnTouchListener { michael@0: private static final String LOGTAG = "GeckoTextSelectionHandle"; michael@0: michael@0: private enum HandleType { START, MIDDLE, END }; michael@0: michael@0: private final HandleType mHandleType; michael@0: private final int mWidth; michael@0: private final int mHeight; michael@0: private final int mShadow; michael@0: michael@0: private float mLeft; michael@0: private float mTop; michael@0: private boolean mIsRTL; michael@0: private PointF mGeckoPoint; michael@0: private float mTouchStartX; michael@0: private float mTouchStartY; michael@0: private int mLayerViewX; michael@0: private int mLayerViewY; michael@0: michael@0: private RelativeLayout.LayoutParams mLayoutParams; michael@0: michael@0: private static final int IMAGE_LEVEL_LTR = 0; michael@0: private static final int IMAGE_LEVEL_RTL = 1; michael@0: michael@0: public TextSelectionHandle(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: setOnTouchListener(this); michael@0: michael@0: TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextSelectionHandle); michael@0: int handleType = a.getInt(R.styleable.TextSelectionHandle_handleType, 0x01); michael@0: michael@0: if (handleType == 0x01) michael@0: mHandleType = HandleType.START; michael@0: else if (handleType == 0x02) michael@0: mHandleType = HandleType.MIDDLE; michael@0: else michael@0: mHandleType = HandleType.END; michael@0: michael@0: mIsRTL = false; michael@0: mGeckoPoint = new PointF(0.0f, 0.0f); michael@0: michael@0: mWidth = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_width); michael@0: mHeight = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_height); michael@0: mShadow = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_shadow); michael@0: } michael@0: michael@0: @Override michael@0: public boolean onTouch(View v, MotionEvent event) { michael@0: switch (event.getActionMasked()) { michael@0: case MotionEvent.ACTION_DOWN: { michael@0: mTouchStartX = event.getX(); michael@0: mTouchStartY = event.getY(); michael@0: michael@0: int[] rect = new int[2]; michael@0: GeckoAppShell.getLayerView().getLocationOnScreen(rect); michael@0: mLayerViewX = rect[0]; michael@0: mLayerViewY = rect[1]; michael@0: break; michael@0: } michael@0: case MotionEvent.ACTION_UP: { michael@0: mTouchStartX = 0; michael@0: mTouchStartY = 0; michael@0: michael@0: // Reposition handles to line up with ends of selection michael@0: JSONObject args = new JSONObject(); michael@0: try { michael@0: args.put("handleType", mHandleType.toString()); michael@0: } catch (Exception e) { michael@0: Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Position"); michael@0: } michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Position", args.toString())); michael@0: break; michael@0: } michael@0: case MotionEvent.ACTION_MOVE: { michael@0: move(event.getRawX(), event.getRawY()); michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: private void move(float newX, float newY) { michael@0: // newX and newY are absolute coordinates, so we need to adjust them to michael@0: // account for other views on the screen (such as the URL bar). We also michael@0: // need to include the offset amount of the touch location relative to michael@0: // the top left of the handle (mTouchStartX and mTouchStartY). michael@0: mLeft = newX - mLayerViewX - mTouchStartX; michael@0: mTop = newY - mLayerViewY - mTouchStartY; michael@0: michael@0: LayerView layerView = GeckoAppShell.getLayerView(); michael@0: if (layerView == null) { michael@0: Log.e(LOGTAG, "Can't move selection because layerView is null"); michael@0: return; michael@0: } michael@0: // Send x coordinate on the right side of the start handle, left side of the end handle. michael@0: float left = mLeft + adjustLeftForHandle(); michael@0: michael@0: PointF geckoPoint = new PointF(left, mTop); michael@0: geckoPoint = layerView.convertViewPointToLayerPoint(geckoPoint); michael@0: michael@0: JSONObject args = new JSONObject(); michael@0: try { michael@0: args.put("handleType", mHandleType.toString()); michael@0: args.put("x", (int) geckoPoint.x); michael@0: args.put("y", (int) geckoPoint.y); michael@0: } catch (Exception e) { michael@0: Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Move"); michael@0: } michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Move", args.toString())); michael@0: michael@0: // If we're positioning a cursor, don't move the handle here. Gecko michael@0: // will tell us the position of the caret, so we set the handle michael@0: // position then. This allows us to lock the handle to wherever the michael@0: // caret appears. michael@0: if (!mHandleType.equals(HandleType.MIDDLE)) { michael@0: setLayoutPosition(); michael@0: } michael@0: } michael@0: michael@0: void positionFromGecko(int left, int top, boolean rtl) { michael@0: LayerView layerView = GeckoAppShell.getLayerView(); michael@0: if (layerView == null) { michael@0: Log.e(LOGTAG, "Can't position handle because layerView is null"); michael@0: return; michael@0: } michael@0: michael@0: mGeckoPoint = new PointF(left, top); michael@0: if (mIsRTL != rtl) { michael@0: mIsRTL = rtl; michael@0: setImageLevel(mIsRTL ? IMAGE_LEVEL_RTL : IMAGE_LEVEL_LTR); michael@0: } michael@0: michael@0: ImmutableViewportMetrics metrics = layerView.getViewportMetrics(); michael@0: PointF offset = metrics.getMarginOffset(); michael@0: repositionWithViewport(metrics.viewportRectLeft - offset.x, metrics.viewportRectTop - offset.y, metrics.zoomFactor); michael@0: } michael@0: michael@0: void repositionWithViewport(float x, float y, float zoom) { michael@0: PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x, michael@0: (mGeckoPoint.y * zoom) - y); michael@0: michael@0: mLeft = viewPoint.x - adjustLeftForHandle(); michael@0: mTop = viewPoint.y; michael@0: michael@0: setLayoutPosition(); michael@0: } michael@0: michael@0: private float adjustLeftForHandle() { michael@0: if (mHandleType.equals(HandleType.START)) michael@0: return mIsRTL ? mShadow : mWidth - mShadow; michael@0: else if (mHandleType.equals(HandleType.MIDDLE)) michael@0: return mWidth / 2; michael@0: else michael@0: return mIsRTL ? mWidth - mShadow : mShadow; michael@0: } michael@0: michael@0: private void setLayoutPosition() { michael@0: if (mLayoutParams == null) { michael@0: mLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); michael@0: // Set negative right/bottom margins so that the handles can be dragged outside of michael@0: // the content area (if they are dragged to the left/top, the dyanmic margins set michael@0: // below will take care of that). michael@0: mLayoutParams.rightMargin = 0 - mWidth; michael@0: mLayoutParams.bottomMargin = 0 - mHeight; michael@0: } michael@0: michael@0: mLayoutParams.leftMargin = (int) mLeft; michael@0: mLayoutParams.topMargin = (int) mTop; michael@0: setLayoutParams(mLayoutParams); michael@0: } michael@0: }