OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser.input; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.content.res.TypedArray; |
| 9 import android.util.TypedValue; |
| 10 import android.view.Gravity; |
| 11 import android.view.LayoutInflater; |
| 12 import android.view.View; |
| 13 import android.view.View.OnClickListener; |
| 14 import android.view.ViewGroup; |
| 15 import android.view.ViewGroup.LayoutParams; |
| 16 import android.widget.PopupWindow; |
| 17 |
| 18 /** |
| 19 * Paste popup implementation based on TextView.PastePopupMenu. |
| 20 */ |
| 21 public class PastePopupMenu implements OnClickListener { |
| 22 private final View mParent; |
| 23 private final PastePopupMenuDelegate mDelegate; |
| 24 private final Context mContext; |
| 25 private final PopupWindow mContainer; |
| 26 private int mRawPositionX; |
| 27 private int mRawPositionY; |
| 28 private int mPositionX; |
| 29 private int mPositionY; |
| 30 private final View[] mPasteViews; |
| 31 private final int[] mPasteViewLayouts; |
| 32 private final int mLineOffsetY; |
| 33 private final int mWidthOffsetX; |
| 34 |
| 35 /** |
| 36 * Provider of paste functionality for the given popup. |
| 37 */ |
| 38 public interface PastePopupMenuDelegate { |
| 39 /** |
| 40 * Called to initiate a paste after the popup has been tapped. |
| 41 */ |
| 42 void paste(); |
| 43 |
| 44 /** |
| 45 * @return true iff content can be pasted to a focused editable region. |
| 46 */ |
| 47 boolean canPaste(); |
| 48 } |
| 49 |
| 50 public PastePopupMenu(View parent, PastePopupMenuDelegate delegate) { |
| 51 mParent = parent; |
| 52 mDelegate = delegate; |
| 53 mContext = parent.getContext(); |
| 54 mContainer = new PopupWindow(mContext, null, |
| 55 android.R.attr.textSelectHandleWindowStyle); |
| 56 mContainer.setSplitTouchEnabled(true); |
| 57 mContainer.setClippingEnabled(false); |
| 58 mContainer.setAnimationStyle(0); |
| 59 |
| 60 mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); |
| 61 mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); |
| 62 |
| 63 final int[] POPUP_LAYOUT_ATTRS = { |
| 64 android.R.attr.textEditPasteWindowLayout, |
| 65 android.R.attr.textEditNoPasteWindowLayout, |
| 66 android.R.attr.textEditSidePasteWindowLayout, |
| 67 android.R.attr.textEditSideNoPasteWindowLayout, |
| 68 }; |
| 69 |
| 70 mPasteViews = new View[POPUP_LAYOUT_ATTRS.length]; |
| 71 mPasteViewLayouts = new int[POPUP_LAYOUT_ATTRS.length]; |
| 72 |
| 73 TypedArray attrs = mContext.getTheme().obtainStyledAttributes(POPUP_LAYO
UT_ATTRS); |
| 74 for (int i = 0; i < attrs.length(); ++i) { |
| 75 mPasteViewLayouts[i] = attrs.getResourceId(attrs.getIndex(i), 0); |
| 76 } |
| 77 attrs.recycle(); |
| 78 |
| 79 // Convert line offset dips to pixels. |
| 80 mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_D
IP, |
| 81 5.0f, mContext.getResources().getDisplayMetrics()); |
| 82 mWidthOffsetX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_
DIP, |
| 83 30.0f, mContext.getResources().getDisplayMetrics()); |
| 84 } |
| 85 |
| 86 /** |
| 87 * Shows the paste popup at an appropriate location relative to the specifie
d position. |
| 88 */ |
| 89 public void showAt(int x, int y) { |
| 90 if (!canPaste()) return; |
| 91 updateContent(true); |
| 92 positionAt(x, y); |
| 93 } |
| 94 |
| 95 /** |
| 96 * Hides the paste popup. |
| 97 */ |
| 98 public void hide() { |
| 99 mContainer.dismiss(); |
| 100 } |
| 101 |
| 102 /** |
| 103 * @return Whether the popup is active and showing. |
| 104 */ |
| 105 public boolean isShowing() { |
| 106 return mContainer.isShowing(); |
| 107 } |
| 108 |
| 109 @Override |
| 110 public void onClick(View v) { |
| 111 if (canPaste()) { |
| 112 paste(); |
| 113 } |
| 114 hide(); |
| 115 } |
| 116 |
| 117 private void positionAt(int x, int y) { |
| 118 if (mRawPositionX == x && mRawPositionY == y && isShowing()) return; |
| 119 mRawPositionX = x; |
| 120 mRawPositionY = y; |
| 121 |
| 122 View contentView = mContainer.getContentView(); |
| 123 int width = contentView.getMeasuredWidth(); |
| 124 int height = contentView.getMeasuredHeight(); |
| 125 |
| 126 mPositionX = (int) (x - width / 2.0f); |
| 127 mPositionY = y - height - mLineOffsetY; |
| 128 |
| 129 final int[] coords = new int[2]; |
| 130 mParent.getLocationInWindow(coords); |
| 131 coords[0] += mPositionX; |
| 132 coords[1] += mPositionY; |
| 133 |
| 134 final int screenWidth = mContext.getResources().getDisplayMetrics().widt
hPixels; |
| 135 if (coords[1] < 0) { |
| 136 updateContent(false); |
| 137 // Update dimensions from new view |
| 138 contentView = mContainer.getContentView(); |
| 139 width = contentView.getMeasuredWidth(); |
| 140 height = contentView.getMeasuredHeight(); |
| 141 |
| 142 // Vertical clipping, move under edited line and to the side of inse
rtion cursor |
| 143 // TODO bottom clipping in case there is no system bar |
| 144 coords[1] += height; |
| 145 coords[1] += mLineOffsetY; |
| 146 |
| 147 // Move to right hand side of insertion cursor by default. TODO RTL
text. |
| 148 final int handleHalfWidth = mWidthOffsetX / 2; |
| 149 |
| 150 if (x + width < screenWidth) { |
| 151 coords[0] += handleHalfWidth + width / 2; |
| 152 } else { |
| 153 coords[0] -= handleHalfWidth + width / 2; |
| 154 } |
| 155 } else { |
| 156 // Horizontal clipping |
| 157 coords[0] = Math.max(0, coords[0]); |
| 158 coords[0] = Math.min(screenWidth - width, coords[0]); |
| 159 } |
| 160 |
| 161 mContainer.showAtLocation(mParent, Gravity.NO_GRAVITY, coords[0], coords
[1]); |
| 162 } |
| 163 |
| 164 private int viewIndex(boolean onTop) { |
| 165 return (onTop ? 0 : 1 << 1) + (canPaste() ? 0 : 1 << 0); |
| 166 } |
| 167 |
| 168 private void updateContent(boolean onTop) { |
| 169 final int viewIndex = viewIndex(onTop); |
| 170 View view = mPasteViews[viewIndex]; |
| 171 |
| 172 if (view == null) { |
| 173 final int layout = mPasteViewLayouts[viewIndex]; |
| 174 LayoutInflater inflater = (LayoutInflater) mContext. |
| 175 getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| 176 if (inflater != null) { |
| 177 view = inflater.inflate(layout, null); |
| 178 } |
| 179 |
| 180 if (view == null) { |
| 181 throw new IllegalArgumentException("Unable to inflate TextEdit p
aste window"); |
| 182 } |
| 183 |
| 184 final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpe
c.UNSPECIFIED); |
| 185 view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CO
NTENT, |
| 186 ViewGroup.LayoutParams.WRAP_CONTENT)); |
| 187 view.measure(size, size); |
| 188 |
| 189 view.setOnClickListener(this); |
| 190 |
| 191 mPasteViews[viewIndex] = view; |
| 192 } |
| 193 |
| 194 mContainer.setContentView(view); |
| 195 } |
| 196 |
| 197 private boolean canPaste() { |
| 198 return mDelegate.canPaste(); |
| 199 } |
| 200 |
| 201 private void paste() { |
| 202 mDelegate.paste(); |
| 203 } |
| 204 } |
OLD | NEW |