mobile/android/base/TextSelectionHandle.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/TextSelectionHandle.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,192 @@
     1.4 + /* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko;
     1.9 +
    1.10 +import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
    1.11 +import org.mozilla.gecko.gfx.LayerView;
    1.12 +
    1.13 +import org.json.JSONObject;
    1.14 +
    1.15 +import android.content.Context;
    1.16 +import android.content.res.TypedArray;
    1.17 +import android.graphics.PointF;
    1.18 +import android.util.AttributeSet;
    1.19 +import android.util.Log;
    1.20 +import android.view.MotionEvent;
    1.21 +import android.view.View;
    1.22 +import android.widget.ImageView;
    1.23 +import android.widget.RelativeLayout;
    1.24 +
    1.25 +class TextSelectionHandle extends ImageView implements View.OnTouchListener {
    1.26 +    private static final String LOGTAG = "GeckoTextSelectionHandle";
    1.27 +
    1.28 +    private enum HandleType { START, MIDDLE, END }; 
    1.29 +
    1.30 +    private final HandleType mHandleType;
    1.31 +    private final int mWidth;
    1.32 +    private final int mHeight;
    1.33 +    private final int mShadow;
    1.34 +
    1.35 +    private float mLeft;
    1.36 +    private float mTop;
    1.37 +    private boolean mIsRTL; 
    1.38 +    private PointF mGeckoPoint;
    1.39 +    private float mTouchStartX;
    1.40 +    private float mTouchStartY;
    1.41 +    private int mLayerViewX;
    1.42 +    private int mLayerViewY;
    1.43 +
    1.44 +    private RelativeLayout.LayoutParams mLayoutParams;
    1.45 +
    1.46 +    private static final int IMAGE_LEVEL_LTR = 0;
    1.47 +    private static final int IMAGE_LEVEL_RTL = 1;
    1.48 +
    1.49 +    public TextSelectionHandle(Context context, AttributeSet attrs) {
    1.50 +        super(context, attrs);
    1.51 +        setOnTouchListener(this);
    1.52 +
    1.53 +        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextSelectionHandle);
    1.54 +        int handleType = a.getInt(R.styleable.TextSelectionHandle_handleType, 0x01);
    1.55 +
    1.56 +        if (handleType == 0x01)
    1.57 +            mHandleType = HandleType.START;
    1.58 +        else if (handleType == 0x02)
    1.59 +            mHandleType = HandleType.MIDDLE;
    1.60 +        else
    1.61 +            mHandleType = HandleType.END;
    1.62 +
    1.63 +        mIsRTL = false;
    1.64 +        mGeckoPoint = new PointF(0.0f, 0.0f);
    1.65 +
    1.66 +        mWidth = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_width);
    1.67 +        mHeight = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_height);
    1.68 +        mShadow = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_shadow);
    1.69 +    }
    1.70 +
    1.71 +    @Override
    1.72 +    public boolean onTouch(View v, MotionEvent event) {
    1.73 +        switch (event.getActionMasked()) {
    1.74 +            case MotionEvent.ACTION_DOWN: {
    1.75 +                mTouchStartX = event.getX();
    1.76 +                mTouchStartY = event.getY();
    1.77 +
    1.78 +                int[] rect = new int[2];
    1.79 +                GeckoAppShell.getLayerView().getLocationOnScreen(rect);
    1.80 +                mLayerViewX = rect[0];
    1.81 +                mLayerViewY = rect[1];
    1.82 +                break;
    1.83 +            }
    1.84 +            case MotionEvent.ACTION_UP: {
    1.85 +                mTouchStartX = 0;
    1.86 +                mTouchStartY = 0;
    1.87 +
    1.88 +                // Reposition handles to line up with ends of selection
    1.89 +                JSONObject args = new JSONObject();
    1.90 +                try {
    1.91 +                    args.put("handleType", mHandleType.toString());
    1.92 +                } catch (Exception e) {
    1.93 +                    Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Position");
    1.94 +                }
    1.95 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Position", args.toString()));
    1.96 +                break;
    1.97 +            }
    1.98 +            case MotionEvent.ACTION_MOVE: {
    1.99 +                move(event.getRawX(), event.getRawY());
   1.100 +                break;
   1.101 +            }
   1.102 +        }
   1.103 +        return true;
   1.104 +    }
   1.105 +
   1.106 +    private void move(float newX, float newY) {
   1.107 +        // newX and newY are absolute coordinates, so we need to adjust them to
   1.108 +        // account for other views on the screen (such as the URL bar). We also
   1.109 +        // need to include the offset amount of the touch location relative to
   1.110 +        // the top left of the handle (mTouchStartX and mTouchStartY).
   1.111 +        mLeft = newX - mLayerViewX - mTouchStartX;
   1.112 +        mTop = newY - mLayerViewY - mTouchStartY;
   1.113 +
   1.114 +        LayerView layerView = GeckoAppShell.getLayerView();
   1.115 +        if (layerView == null) {
   1.116 +            Log.e(LOGTAG, "Can't move selection because layerView is null");
   1.117 +            return;
   1.118 +        }
   1.119 +        // Send x coordinate on the right side of the start handle, left side of the end handle.
   1.120 +        float left = mLeft + adjustLeftForHandle();
   1.121 +
   1.122 +        PointF geckoPoint = new PointF(left, mTop);
   1.123 +        geckoPoint = layerView.convertViewPointToLayerPoint(geckoPoint);
   1.124 +
   1.125 +        JSONObject args = new JSONObject();
   1.126 +        try {
   1.127 +            args.put("handleType", mHandleType.toString());
   1.128 +            args.put("x", (int) geckoPoint.x);
   1.129 +            args.put("y", (int) geckoPoint.y);
   1.130 +        } catch (Exception e) {
   1.131 +            Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Move");
   1.132 +        }
   1.133 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Move", args.toString()));
   1.134 +
   1.135 +        // If we're positioning a cursor, don't move the handle here. Gecko
   1.136 +        // will tell us the position of the caret, so we set the handle
   1.137 +        // position then. This allows us to lock the handle to wherever the
   1.138 +        // caret appears.
   1.139 +        if (!mHandleType.equals(HandleType.MIDDLE)) {
   1.140 +            setLayoutPosition();
   1.141 +        }
   1.142 +    }
   1.143 +
   1.144 +    void positionFromGecko(int left, int top, boolean rtl) {
   1.145 +        LayerView layerView = GeckoAppShell.getLayerView();
   1.146 +        if (layerView == null) {
   1.147 +            Log.e(LOGTAG, "Can't position handle because layerView is null");
   1.148 +            return;
   1.149 +        }
   1.150 +
   1.151 +        mGeckoPoint = new PointF(left, top);
   1.152 +        if (mIsRTL != rtl) {
   1.153 +            mIsRTL = rtl;
   1.154 +            setImageLevel(mIsRTL ? IMAGE_LEVEL_RTL : IMAGE_LEVEL_LTR);
   1.155 +        }
   1.156 +
   1.157 +        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
   1.158 +        PointF offset = metrics.getMarginOffset();
   1.159 +        repositionWithViewport(metrics.viewportRectLeft - offset.x, metrics.viewportRectTop - offset.y, metrics.zoomFactor);
   1.160 +    }
   1.161 +
   1.162 +    void repositionWithViewport(float x, float y, float zoom) {
   1.163 +        PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x,
   1.164 +                                      (mGeckoPoint.y * zoom) - y);
   1.165 +
   1.166 +        mLeft = viewPoint.x - adjustLeftForHandle();
   1.167 +        mTop = viewPoint.y;
   1.168 +
   1.169 +        setLayoutPosition();
   1.170 +    }
   1.171 +
   1.172 +    private float adjustLeftForHandle() {
   1.173 +        if (mHandleType.equals(HandleType.START))
   1.174 +            return mIsRTL ? mShadow : mWidth - mShadow;
   1.175 +        else if (mHandleType.equals(HandleType.MIDDLE))
   1.176 +            return mWidth / 2;
   1.177 +        else
   1.178 +            return mIsRTL ? mWidth - mShadow : mShadow;
   1.179 +    }
   1.180 +
   1.181 +    private void setLayoutPosition() {
   1.182 +        if (mLayoutParams == null) {
   1.183 +            mLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
   1.184 +            // Set negative right/bottom margins so that the handles can be dragged outside of
   1.185 +            // the content area (if they are dragged to the left/top, the dyanmic margins set
   1.186 +            // below will take care of that).
   1.187 +            mLayoutParams.rightMargin = 0 - mWidth;
   1.188 +            mLayoutParams.bottomMargin = 0 - mHeight;
   1.189 +        }
   1.190 +
   1.191 +        mLayoutParams.leftMargin = (int) mLeft;
   1.192 +        mLayoutParams.topMargin = (int) mTop;
   1.193 +        setLayoutParams(mLayoutParams);
   1.194 +    }
   1.195 +}

mercurial