├── .gitignore ├── LICENSE ├── README.md ├── library ├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── project.properties ├── res │ ├── drawable-hdpi │ │ ├── text_select_handle_left.png │ │ └── text_select_handle_right.png │ ├── drawable-mdpi │ │ ├── text_select_handle_left.png │ │ └── text_select_handle_right.png │ ├── drawable-xhdpi │ │ ├── text_select_handle_left.png │ │ └── text_select_handle_right.png │ ├── layout │ │ └── selection_drag_layer.xml │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── com │ ├── blahti │ └── drag │ │ ├── DragController.java │ │ ├── DragLayer.java │ │ ├── DragListener.java │ │ ├── DragSource.java │ │ ├── DragView.java │ │ ├── DropTarget.java │ │ └── MyAbsoluteLayout.java │ └── bossturban │ └── webviewmarker │ ├── TextSelectionControlListener.java │ ├── TextSelectionController.java │ └── TextSelectionSupport.java └── samples └── demos ├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── assets ├── android.selection.js ├── content.html ├── css │ └── sample.css ├── jpntext.js ├── jquery-1.8.3.js ├── rangy-core.js └── rangy-serializer.js ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ └── main.xml └── values │ └── strings.xml └── src └── com └── bossturban └── webviewmarker └── sample └── demos └── MainActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 - 2015 Naoaki Yamada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebViewMarker 2 | ============= 3 | 4 | This library provides text selection support for Android WebView. The core logic is the same as [BTAndroidWebViewSelection](https://github.com/btate/BTAndroidWebViewSelection). 5 | 6 | ## Features 7 | - More modular (not inherit webview) 8 | - Not depend quick action lib (for actionbar support) 9 | - Mimic ICS native text selection handlers 10 | - Smart tap selection for CJK characters 11 | 12 | ## Supported Android Versions 13 | Tested on API 8-21 (Android 2.2-5.0.1). 14 | 15 | ## How to use 16 | See `samples/demos` directory. 17 | 18 | ##How to add to Gradle 19 | Add the following to your build file: 20 | ``` 21 | dependencies { 22 | //other dependencies also go here 23 | compile 'com.github.naoak:WebViewMarker:v0.1.3' 24 | } 25 | repositories { 26 | //other repositories 27 | maven { url "https://jitpack.io" } 28 | } 29 | ``` 30 | Then just sync Gradle with the project 31 | 32 | ## Included Libraries 33 | This project uses the following libraries. 34 | - [rangy](https://github.com/timdown/rangy) by Tim Down 35 | - [drag and drop](https://blahti.wordpress.com/2011/01/17/moving-views-part-2/) library by Bill Lahti 36 | 37 | ## License 38 | MIT License. 39 | -------------------------------------------------------------------------------- /library/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | -------------------------------------------------------------------------------- /library/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebViewMarker 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | android.library=true 11 | # Project target. 12 | target=android-19 13 | -------------------------------------------------------------------------------- /library/res/drawable-hdpi/text_select_handle_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-hdpi/text_select_handle_left.png -------------------------------------------------------------------------------- /library/res/drawable-hdpi/text_select_handle_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-hdpi/text_select_handle_right.png -------------------------------------------------------------------------------- /library/res/drawable-mdpi/text_select_handle_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-mdpi/text_select_handle_left.png -------------------------------------------------------------------------------- /library/res/drawable-mdpi/text_select_handle_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-mdpi/text_select_handle_right.png -------------------------------------------------------------------------------- /library/res/drawable-xhdpi/text_select_handle_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-xhdpi/text_select_handle_left.png -------------------------------------------------------------------------------- /library/res/drawable-xhdpi/text_select_handle_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/library/res/drawable-xhdpi/text_select_handle_right.png -------------------------------------------------------------------------------- /library/res/layout/selection_drag_layer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /library/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /library/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DragController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android 3 | * Open Source Project. The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.content.Context; 23 | import android.graphics.Bitmap; 24 | import android.graphics.Rect; 25 | import android.os.IBinder; 26 | import android.util.DisplayMetrics; 27 | import android.util.Log; 28 | import android.view.View; 29 | import android.view.KeyEvent; 30 | import android.view.MotionEvent; 31 | import android.view.WindowManager; 32 | import android.view.inputmethod.InputMethodManager; 33 | 34 | import java.util.ArrayList; 35 | 36 | /** 37 | * This class is used to initiate a drag within a view or across multiple views. 38 | * When a drag starts it creates a special view (a DragView) that moves around the screen 39 | * until the user ends the drag. As feedback to the user, this object causes the device to 40 | * vibrate as the drag begins. 41 | * 42 | */ 43 | 44 | public class DragController { 45 | public enum DragBehavior { 46 | MOVE, // indicates the drag is move 47 | COPY // indicates the drag is copy 48 | } 49 | public static final String TAG = "DragController"; 50 | 51 | private Context mContext; 52 | private Rect mRectTemp = new Rect(); 53 | private final int[] mCoordinatesTemp = new int[2]; 54 | private boolean mDragging; 55 | private float mMotionDownX; 56 | private float mMotionDownY; 57 | private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 58 | 59 | /** Original view that is being dragged. */ 60 | private View mOriginator; 61 | 62 | /** X offset from the upper-left corner of the cell to where we touched. */ 63 | private float mTouchOffsetX; 64 | 65 | /** Y offset from the upper-left corner of the cell to where we touched. */ 66 | private float mTouchOffsetY; 67 | 68 | /** Where the drag originated */ 69 | private DragSource mDragSource; 70 | 71 | /** The data associated with the object being dragged */ 72 | private Object mDragInfo; 73 | 74 | /** The view that moves around while you drag. */ 75 | private DragView mDragView; 76 | 77 | /** Who can receive drop events */ 78 | private ArrayList mDropTargets = new ArrayList(); 79 | 80 | private DragListener mListener; 81 | 82 | /** The window token used as the parent for the DragView. */ 83 | private IBinder mWindowToken; 84 | 85 | private View mMoveTarget; 86 | 87 | private DropTarget mLastDropTarget; 88 | 89 | private InputMethodManager mInputMethodManager; 90 | 91 | 92 | /** 93 | * Used to create a new DragLayer from XML. 94 | * 95 | * @param context The application's context. 96 | */ 97 | public DragController(Context context) { 98 | mContext = context; 99 | } 100 | 101 | /** 102 | * Starts a drag. 103 | * It creates a bitmap of the view being dragged. That bitmap is what you see moving. 104 | * The actual view can be repositioned if that is what the onDrop handle chooses to do. 105 | * 106 | * @param v The view that is being dragged 107 | * @param source An object representing where the drag originated 108 | * @param dragInfo The data associated with the object that is being dragged 109 | * @param dragAction The drag behavior: move or copy 110 | */ 111 | public void startDrag(View v, DragSource source, Object dragInfo, DragBehavior dragBehavior) { 112 | if (source.allowDrag()) { 113 | mOriginator = v; 114 | final Bitmap b = getViewBitmap(v); 115 | if (b != null) { 116 | final int[] loc = mCoordinatesTemp; 117 | v.getLocationOnScreen(loc); 118 | final int screenX = loc[0]; 119 | final int screenY = loc[1]; 120 | startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(), source, dragInfo, dragBehavior); 121 | b.recycle(); 122 | if (dragBehavior == DragBehavior.MOVE) { 123 | v.setVisibility(View.GONE); 124 | } 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Starts a drag. 131 | * 132 | * @param b The bitmap to display as the drag image. It will be re-scaled to the 133 | * enlarged size. 134 | * @param screenX The x position on screen of the left-top of the bitmap. 135 | * @param screenY The y position on screen of the left-top of the bitmap. 136 | * @param textureLeft The left edge of the region inside b to use. 137 | * @param textureTop The top edge of the region inside b to use. 138 | * @param textureWidth The width of the region inside b to use. 139 | * @param textureHeight The height of the region inside b to use. 140 | * @param source An object representing where the drag originated 141 | * @param dragInfo The data associated with the object that is being dragged 142 | * @param dragBehavior The drag action: move or copy 143 | */ 144 | private void startDrag(Bitmap b, int screenX, int screenY, int textureLeft, int textureTop, int textureWidth, int textureHeight, DragSource source, Object dragInfo, DragBehavior dragBehavior) { 145 | if (mInputMethodManager == null) { 146 | mInputMethodManager = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 147 | } 148 | mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0); 149 | if (mListener != null) { 150 | mListener.onDragStart(source, dragInfo, dragBehavior); 151 | } 152 | final int registrationX = ((int)mMotionDownX) - screenX; 153 | final int registrationY = ((int)mMotionDownY) - screenY; 154 | mTouchOffsetX = mMotionDownX - screenX; 155 | mTouchOffsetY = mMotionDownY - screenY; 156 | mDragging = true; 157 | mDragSource = source; 158 | mDragInfo = dragInfo; 159 | mDragView = new DragView(mContext, b, registrationX, registrationY, textureLeft, textureTop, textureWidth, textureHeight); 160 | mDragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY); 161 | } 162 | 163 | /** 164 | * Draw the view into a bitmap. 165 | */ 166 | private Bitmap getViewBitmap(View v) { 167 | v.clearFocus(); 168 | v.setPressed(false); 169 | 170 | boolean willNotCache = v.willNotCacheDrawing(); 171 | v.setWillNotCacheDrawing(false); 172 | 173 | // Reset the drawing cache background color to fully transparent 174 | // for the duration of this operation 175 | int color = v.getDrawingCacheBackgroundColor(); 176 | v.setDrawingCacheBackgroundColor(0); 177 | 178 | if (color != 0) { 179 | v.destroyDrawingCache(); 180 | } 181 | v.buildDrawingCache(); 182 | Bitmap cacheBitmap = v.getDrawingCache(); 183 | if (cacheBitmap == null) { 184 | Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); 185 | return null; 186 | } 187 | 188 | Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); 189 | 190 | // Restore the view 191 | v.destroyDrawingCache(); 192 | v.setWillNotCacheDrawing(willNotCache); 193 | v.setDrawingCacheBackgroundColor(color); 194 | 195 | return bitmap; 196 | } 197 | 198 | public boolean dispatchKeyEvent(KeyEvent event) { 199 | return mDragging; 200 | } 201 | 202 | public void cancelDrag() { 203 | endDrag(); 204 | } 205 | 206 | private void endDrag() { 207 | if (mDragging) { 208 | mDragging = false; 209 | if (mOriginator != null) { 210 | mOriginator.setVisibility(View.VISIBLE); 211 | } 212 | if (mListener != null) { 213 | mListener.onDragEnd(); 214 | } 215 | if (mDragView != null) { 216 | mDragView.remove(); 217 | mDragView = null; 218 | } 219 | } 220 | } 221 | 222 | public boolean onInterceptTouchEvent(MotionEvent ev) { 223 | final int action = ev.getAction(); 224 | if (action == MotionEvent.ACTION_DOWN) { 225 | recordScreenSize(); 226 | } 227 | final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels); 228 | final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels); 229 | switch (action) { 230 | case MotionEvent.ACTION_MOVE: 231 | break; 232 | case MotionEvent.ACTION_DOWN: 233 | mMotionDownX = screenX; 234 | mMotionDownY = screenY; 235 | mLastDropTarget = null; 236 | break; 237 | case MotionEvent.ACTION_CANCEL: 238 | case MotionEvent.ACTION_UP: 239 | if (mDragging) { 240 | drop(screenX, screenY); 241 | } 242 | endDrag(); 243 | break; 244 | } 245 | return mDragging; 246 | } 247 | 248 | /** 249 | * Sets the view that should handle move events. 250 | */ 251 | void setMoveTarget(View view) { 252 | mMoveTarget = view; 253 | } 254 | 255 | public boolean dispatchUnhandledMove(View focused, int direction) { 256 | return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction); 257 | } 258 | 259 | public boolean onTouchEvent(MotionEvent ev) { 260 | if (!mDragging) { 261 | return false; 262 | } 263 | 264 | final int action = ev.getAction(); 265 | final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels); 266 | final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels); 267 | 268 | switch (action) { 269 | case MotionEvent.ACTION_DOWN: 270 | mMotionDownX = screenX; 271 | mMotionDownY = screenY; 272 | break; 273 | case MotionEvent.ACTION_MOVE: 274 | mDragView.move((int)ev.getRawX(), (int)ev.getRawY()); 275 | final int[] coordinates = mCoordinatesTemp; 276 | DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates); 277 | if (dropTarget != null) { 278 | if (mLastDropTarget == dropTarget) { 279 | dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1], (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); 280 | } 281 | else { 282 | if (mLastDropTarget != null) { 283 | mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo); 284 | } 285 | dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1], (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); 286 | } 287 | } 288 | else { 289 | if (mLastDropTarget != null) { 290 | mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); 291 | } 292 | } 293 | mLastDropTarget = dropTarget; 294 | break; 295 | case MotionEvent.ACTION_UP: 296 | if (mDragging) { 297 | drop(screenX, screenY); 298 | } 299 | endDrag(); 300 | break; 301 | case MotionEvent.ACTION_CANCEL: 302 | cancelDrag(); 303 | } 304 | 305 | return true; 306 | } 307 | 308 | private boolean drop(float x, float y) { 309 | final int[] coordinates = mCoordinatesTemp; 310 | final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); 311 | if (dropTarget != null) { 312 | dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); 313 | if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo)) { 314 | dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1], (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo); 315 | mDragSource.onDropCompleted((View)dropTarget, true); 316 | } 317 | else { 318 | mDragSource.onDropCompleted((View)dropTarget, false); 319 | } 320 | return true; 321 | } 322 | return false; 323 | } 324 | private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { 325 | final Rect r = mRectTemp; 326 | final ArrayList dropTargets = mDropTargets; 327 | final int count = dropTargets.size(); 328 | for (int i = count - 1; i >= 0; i--) { 329 | final DropTarget target = dropTargets.get(i); 330 | target.getHitRect(r); 331 | target.getLocationOnScreen(dropCoordinates); 332 | r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop()); 333 | if (r.contains(x, y)) { 334 | dropCoordinates[0] = x - dropCoordinates[0]; 335 | dropCoordinates[1] = y - dropCoordinates[1]; 336 | return target; 337 | } 338 | } 339 | return null; 340 | } 341 | 342 | private void recordScreenSize() { 343 | ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(mDisplayMetrics); 344 | } 345 | private static int clamp(int val, int min, int max) { 346 | if (val < min) { 347 | return min; 348 | } 349 | else if (val >= max) { 350 | return max - 1; 351 | } 352 | else { 353 | return val; 354 | } 355 | } 356 | 357 | public void setDragListener(DragListener listener) { 358 | mListener = listener; 359 | } 360 | public void addDropTarget(DropTarget target) { 361 | mDropTargets.add(target); 362 | } 363 | public void removeDropTarget(DropTarget target) { 364 | mDropTargets.remove(target); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DragLayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android Open Source Project. 3 | * The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.content.Context; 23 | import android.graphics.Rect; 24 | import android.util.AttributeSet; 25 | import android.view.MotionEvent; 26 | import android.view.KeyEvent; 27 | import android.view.View; 28 | 29 | /** 30 | * A ViewGroup that coordinates dragging across its descendants. 31 | * 32 | *

This class used DragLayer in the Android Launcher activity as a model. 33 | * It is a bit different in several respects: 34 | * (1) It extends MyAbsoluteLayout rather than FrameLayout; (2) it implements DragSource and DropTarget methods 35 | * that were done in a separate Workspace class in the Launcher. 36 | */ 37 | public class DragLayer extends MyAbsoluteLayout implements DragSource, DropTarget { 38 | private DragController mDragController; 39 | 40 | public DragLayer (Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | } 43 | @Override 44 | public boolean dispatchKeyEvent(KeyEvent event) { 45 | return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 46 | } 47 | @Override 48 | public boolean onInterceptTouchEvent(MotionEvent ev) { 49 | return mDragController.onInterceptTouchEvent(ev); 50 | } 51 | @Override 52 | public boolean onTouchEvent(MotionEvent ev) { 53 | return mDragController.onTouchEvent(ev); 54 | } 55 | @Override 56 | public boolean dispatchUnhandledMove(View focused, int direction) { 57 | return mDragController.dispatchUnhandledMove(focused, direction); 58 | } 59 | 60 | // Interfaces of DragSource 61 | @Override 62 | public boolean allowDrag() { 63 | return true; 64 | } 65 | @Override 66 | public void setDragController(DragController controller) { 67 | mDragController = controller; 68 | } 69 | @Override 70 | public void onDropCompleted(View target, boolean success) { 71 | } 72 | 73 | // Interfaces of DropTarget 74 | @Override 75 | public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { 76 | final View v = (View)dragInfo; 77 | final int w = v.getWidth(); 78 | final int h = v.getHeight(); 79 | final int left = x - xOffset; 80 | final int top = y - yOffset; 81 | final DragLayer.LayoutParams lp = new DragLayer.LayoutParams (w, h, left, top); 82 | updateViewLayout(v, lp); 83 | } 84 | @Override 85 | public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { 86 | } 87 | @Override 88 | public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { 89 | } 90 | @Override 91 | public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { 92 | } 93 | @Override 94 | public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo) { 95 | return true; 96 | } 97 | @Override 98 | public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) { 99 | return null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DragListener.java: -------------------------------------------------------------------------------- 1 | package com.blahti.drag; 2 | 3 | /** 4 | * Interface to receive notifications when a drag starts or stops 5 | */ 6 | public interface DragListener { 7 | void onDragStart(DragSource source, Object info, DragController.DragBehavior dragBehavior); 8 | void onDragEnd(); 9 | } 10 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DragSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android Open Source Project. 3 | * The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.view.View; 23 | 24 | /** 25 | * Interface defining an object where drag operations originate. 26 | * 27 | */ 28 | public interface DragSource { 29 | 30 | /** 31 | * This method is called to determine if the DragSource has something to drag. 32 | * 33 | * @return True if there is something to drag 34 | */ 35 | 36 | boolean allowDrag (); 37 | 38 | /** 39 | * This method is used to tell the DragSource which drag controller it is working with. 40 | * 41 | * @param dragger DragController 42 | */ 43 | 44 | void setDragController(DragController dragger); 45 | 46 | /** 47 | * This method is called on the completion of the drag operation so the DragSource knows 48 | * whether it succeeded or failed. 49 | * 50 | * @param target View - the view that accepted the dragged object 51 | * @param success boolean - true means that the object was dropped successfully 52 | */ 53 | 54 | void onDropCompleted (View target, boolean success); 55 | } 56 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DragView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android Open Source Project. 3 | * The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.content.Context; 23 | import android.graphics.Bitmap; 24 | import android.graphics.Canvas; 25 | import android.graphics.Matrix; 26 | import android.graphics.Paint; 27 | import android.graphics.PixelFormat; 28 | import android.os.IBinder; 29 | import android.view.Gravity; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | import android.view.WindowManager; 33 | 34 | /** 35 | * A DragView is a special view used by a DragController. During a drag operation, what is actually moving 36 | * on the screen is a DragView. A DragView is constructed using a bitmap of the view the user really 37 | * wants to move. 38 | * 39 | */ 40 | 41 | public class DragView extends View { 42 | private static final boolean DEBUG = false; 43 | private static final int PADDING_TO_SCALE = 0; 44 | private final int mRegistrationX; 45 | private final int mRegistrationY; 46 | private Bitmap mBitmap; 47 | private Paint mDebugPaint = new Paint(); 48 | private WindowManager.LayoutParams mLayoutParams; 49 | private WindowManager mWindowManager; 50 | 51 | public DragView(Context context) throws Exception { 52 | super(context); 53 | mRegistrationX = 0; 54 | mRegistrationY = 0; 55 | throw new Exception("DragView constructor permits only programatical calling"); 56 | } 57 | 58 | /** 59 | * Construct the drag view. 60 | *

61 | * The registration point is the point inside our view that the touch events should 62 | * be centered upon. 63 | * 64 | * @param context A context 65 | * @param bitmap The view that we're dragging around. We scale it up when we draw it. 66 | * @param registrationX The x coordinate of the registration point. 67 | * @param registrationY The y coordinate of the registration point. 68 | */ 69 | public DragView(Context context, Bitmap bitmap, int registrationX, int registrationY, int left, int top, int width, int height) { 70 | super(context); 71 | mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 72 | mRegistrationX = registrationX + (PADDING_TO_SCALE / 2); 73 | mRegistrationY = registrationY + (PADDING_TO_SCALE / 2); 74 | final float scaleFactor = ((float)width + PADDING_TO_SCALE) / (float)width; 75 | final Matrix scale = new Matrix(); 76 | scale.setScale(scaleFactor, scaleFactor); 77 | mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true); 78 | } 79 | 80 | @Override 81 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 82 | setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); 83 | } 84 | @Override 85 | protected void onDraw(Canvas canvas) { 86 | if (DEBUG) { 87 | mDebugPaint.setStyle(Paint.Style.FILL); 88 | mDebugPaint.setColor(0x88dd0011); 89 | canvas.drawRect(0, 0, getWidth(), getHeight(), mDebugPaint); 90 | } 91 | canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null); 92 | } 93 | @Override 94 | protected void onDetachedFromWindow() { 95 | super.onDetachedFromWindow(); 96 | mBitmap.recycle(); 97 | } 98 | 99 | void show(IBinder windowToken, int touchX, int touchY) { 100 | final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 101 | ViewGroup.LayoutParams.WRAP_CONTENT, 102 | ViewGroup.LayoutParams.WRAP_CONTENT, 103 | touchX - mRegistrationX, touchY - mRegistrationY, 104 | WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 105 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 106 | PixelFormat.TRANSLUCENT 107 | ); 108 | lp.gravity = Gravity.LEFT | Gravity.TOP; 109 | lp.token = windowToken; 110 | lp.setTitle("DragView"); 111 | mLayoutParams = lp; 112 | mWindowManager.addView(this, lp); 113 | } 114 | void move(int touchX, int touchY) { 115 | WindowManager.LayoutParams lp = mLayoutParams; 116 | lp.x = touchX - mRegistrationX; 117 | lp.y = touchY - mRegistrationY; 118 | mWindowManager.updateViewLayout(this, lp); 119 | } 120 | void remove() { 121 | mWindowManager.removeView(this); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/DropTarget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android Open Source Project. 3 | * The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.graphics.Rect; 23 | 24 | /** 25 | * Interface defining an object that can receive a view at the end of a drag operation. 26 | * 27 | */ 28 | public interface DropTarget { 29 | 30 | /** 31 | * Handle an object being dropped on the DropTarget 32 | * 33 | * @param source DragSource where the drag started 34 | * @param x X coordinate of the drop location 35 | * @param y Y coordinate of the drop location 36 | * @param xOffset Horizontal offset with the object being dragged where the original 37 | * touch happened 38 | * @param yOffset Vertical offset with the object being dragged where the original 39 | * touch happened 40 | * @param dragView The DragView that's being dragged around on screen. 41 | * @param dragInfo Data associated with the object being dragged 42 | * 43 | */ 44 | void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); 45 | 46 | void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); 47 | 48 | void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); 49 | 50 | void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); 51 | 52 | /** 53 | * Check if a drop action can occur at, or near, the requested location. 54 | * This may be called repeatedly during a drag, so any calls should return 55 | * quickly. 56 | * 57 | * @param source DragSource where the drag started 58 | * @param x X coordinate of the drop location 59 | * @param y Y coordinate of the drop location 60 | * @param xOffset Horizontal offset with the object being dragged where the 61 | * original touch happened 62 | * @param yOffset Vertical offset with the object being dragged where the 63 | * original touch happened 64 | * @param dragView The DragView that's being dragged around on screen. 65 | * @param dragInfo Data associated with the object being dragged 66 | * @return True if the drop will be accepted, false otherwise. 67 | */ 68 | boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo); 69 | 70 | /** 71 | * Estimate the surface area where this object would land if dropped at the 72 | * given location. 73 | * 74 | * @param source DragSource where the drag started 75 | * @param x X coordinate of the drop location 76 | * @param y Y coordinate of the drop location 77 | * @param xOffset Horizontal offset with the object being dragged where the 78 | * original touch happened 79 | * @param yOffset Vertical offset with the object being dragged where the 80 | * original touch happened 81 | * @param dragView The DragView that's being dragged around on screen. 82 | * @param dragInfo Data associated with the object being dragged 83 | * @param recycle {@link Rect} object to be possibly recycled. 84 | * @return Estimated area that would be occupied if object was dropped at 85 | * the given location. Should return null if no estimate is found, 86 | * or if this target doesn't provide estimations. 87 | */ 88 | Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle); 89 | 90 | // These methods are implemented in Views 91 | void getHitRect(Rect outRect); 92 | void getLocationOnScreen(int[] loc); 93 | int getLeft(); 94 | int getTop(); 95 | } 96 | -------------------------------------------------------------------------------- /library/src/com/blahti/drag/MyAbsoluteLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a modified version of a class from the Android Open Source Project. 3 | * The original copyright and license information follows. 4 | * 5 | * Copyright (C) 2006 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.blahti.drag; 21 | 22 | import android.content.Context; 23 | import android.util.AttributeSet; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.widget.RemoteViews.RemoteView; 27 | 28 | /** 29 | * A layout that lets you specify exact locations (x/y coordinates) of its 30 | * children. Absolute layouts are less flexible and harder to maintain than 31 | * other types of layouts without absolute positioning. 32 | * 33 | *

XML attributes

See {@link 34 | * android.R.styleable#ViewGroup ViewGroup Attributes}, {@link 35 | * android.R.styleable#View View Attributes}

36 | * 37 | *

Note: This class is a clone of AbsoluteLayout, which is now deprecated. 38 | */ 39 | 40 | @RemoteView 41 | public class MyAbsoluteLayout extends ViewGroup { 42 | public MyAbsoluteLayout(Context context) { 43 | super(context); 44 | } 45 | 46 | public MyAbsoluteLayout(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | public MyAbsoluteLayout(Context context, AttributeSet attrs, 51 | int defStyle) { 52 | super(context, attrs, defStyle); 53 | } 54 | 55 | @Override 56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 57 | int count = getChildCount(); 58 | 59 | int maxHeight = 0; 60 | int maxWidth = 0; 61 | 62 | // Find out how big everyone wants to be 63 | measureChildren(widthMeasureSpec, heightMeasureSpec); 64 | 65 | // Find rightmost and bottom-most child 66 | for (int i = 0; i < count; i++) { 67 | View child = getChildAt(i); 68 | if (child.getVisibility() != GONE) { 69 | int childRight; 70 | int childBottom; 71 | 72 | MyAbsoluteLayout.LayoutParams lp 73 | = (MyAbsoluteLayout.LayoutParams) child.getLayoutParams(); 74 | 75 | childRight = lp.x + child.getMeasuredWidth(); 76 | childBottom = lp.y + child.getMeasuredHeight(); 77 | 78 | maxWidth = Math.max(maxWidth, childRight); 79 | maxHeight = Math.max(maxHeight, childBottom); 80 | } 81 | } 82 | 83 | // Account for padding too 84 | maxWidth += getPaddingLeft () + getPaddingRight (); 85 | maxHeight += getPaddingTop () + getPaddingBottom (); 86 | /* original 87 | maxWidth += mPaddingLeft + mPaddingRight; 88 | maxHeight += mPaddingTop + mPaddingBottom; 89 | */ 90 | 91 | // Check against minimum height and width 92 | maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 93 | maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 94 | 95 | setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), 96 | resolveSize(maxHeight, heightMeasureSpec)); 97 | } 98 | 99 | /** 100 | * Returns a set of layout parameters with a width of 101 | * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, 102 | * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} 103 | * and with the coordinates (0, 0). 104 | */ 105 | @Override 106 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 107 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0); 108 | } 109 | 110 | @Override 111 | protected void onLayout(boolean changed, int l, int t, 112 | int r, int b) { 113 | int count = getChildCount(); 114 | 115 | int paddingL = getPaddingLeft (); 116 | int paddingT = getPaddingTop (); 117 | for (int i = 0; i < count; i++) { 118 | View child = getChildAt(i); 119 | if (child.getVisibility() != GONE) { 120 | 121 | MyAbsoluteLayout.LayoutParams lp = 122 | (MyAbsoluteLayout.LayoutParams) child.getLayoutParams(); 123 | 124 | int childLeft = paddingL + lp.x; 125 | int childTop = paddingT + lp.y; 126 | /* 127 | int childLeft = mPaddingLeft + lp.x; 128 | int childTop = mPaddingTop + lp.y; 129 | */ 130 | child.layout(childLeft, childTop, 131 | childLeft + child.getMeasuredWidth(), 132 | childTop + child.getMeasuredHeight()); 133 | 134 | } 135 | } 136 | } 137 | 138 | @Override 139 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 140 | return new MyAbsoluteLayout.LayoutParams(getContext(), attrs); 141 | } 142 | 143 | // Override to allow type-checking of LayoutParams. 144 | @Override 145 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 146 | return p instanceof MyAbsoluteLayout.LayoutParams; 147 | } 148 | 149 | @Override 150 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 151 | return new LayoutParams(p); 152 | } 153 | 154 | /** 155 | * Per-child layout information associated with MyAbsoluteLayout. 156 | * See 157 | * {@link android.R.styleable#MyAbsoluteLayout_Layout Absolute Layout Attributes} 158 | * for a list of all child view attributes that this class supports. 159 | */ 160 | public static class LayoutParams extends ViewGroup.LayoutParams { 161 | /** 162 | * The horizontal, or X, location of the child within the view group. 163 | */ 164 | public int x; 165 | /** 166 | * The vertical, or Y, location of the child within the view group. 167 | */ 168 | public int y; 169 | 170 | /** 171 | * Creates a new set of layout parameters with the specified width, 172 | * height and location. 173 | * 174 | * @param width the width, either {@link #MATCH_PARENT}, 175 | {@link #WRAP_CONTENT} or a fixed size in pixels 176 | * @param height the height, either {@link #MATCH_PARENT}, 177 | {@link #WRAP_CONTENT} or a fixed size in pixels 178 | * @param x the X location of the child 179 | * @param y the Y location of the child 180 | */ 181 | public LayoutParams(int width, int height, int x, int y) { 182 | super(width, height); 183 | this.x = x; 184 | this.y = y; 185 | } 186 | 187 | /** 188 | * Creates a new set of layout parameters. The values are extracted from 189 | * the supplied attributes set and context. The XML attributes mapped 190 | * to this set of layout parameters are: 191 | * 192 | *

    193 | *
  • layout_x: the X location of the child
  • 194 | *
  • layout_y: the Y location of the child
  • 195 | *
  • All the XML attributes from 196 | * {@link android.view.ViewGroup.LayoutParams}
  • 197 | *
198 | * 199 | * @param c the application environment 200 | * @param attrs the set of attributes from which to extract the layout 201 | * parameters values 202 | */ 203 | public LayoutParams(Context c, AttributeSet attrs) { 204 | super(c, attrs); 205 | /* FIX THIS eventually. Without this, I don't think you can put x and y in layout xml files. 206 | TypedArray a = c.obtainStyledAttributes(attrs, 207 | com.android.internal.R.styleable.AbsoluteLayout_Layout); 208 | x = a.getDimensionPixelOffset( 209 | com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_x, 0); 210 | y = a.getDimensionPixelOffset( 211 | com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_y, 0); 212 | a.recycle(); 213 | */ 214 | } 215 | 216 | /** 217 | * {@inheritDoc} 218 | */ 219 | public LayoutParams(ViewGroup.LayoutParams source) { 220 | super(source); 221 | } 222 | 223 | public String debug(String output) { 224 | return output + "Absolute.LayoutParams={width=" 225 | + sizeToString(width) + ", height=" + sizeToString(height) 226 | + " x=" + x + " y=" + y + "}"; 227 | } 228 | 229 | /** 230 | * Converts the specified size to a readable String. 231 | * 232 | * @param size the size to convert 233 | * @return a String instance representing the supplied size 234 | * 235 | * @hide 236 | */ 237 | protected static String sizeToString(int size) { 238 | if (size == WRAP_CONTENT) { 239 | return "wrap-content"; 240 | } 241 | if (size == MATCH_PARENT) { 242 | return "match-parent"; 243 | } 244 | return String.valueOf(size); 245 | } 246 | } // end class 247 | 248 | } // end class 249 | 250 | 251 | -------------------------------------------------------------------------------- /library/src/com/bossturban/webviewmarker/TextSelectionControlListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 - 2014 Brandon Tate, bossturbo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bossturban.webviewmarker; 18 | 19 | public interface TextSelectionControlListener { 20 | void jsError(String error); 21 | void jsLog(String message); 22 | void startSelectionMode(); 23 | void endSelectionMode(); 24 | 25 | /** 26 | * Tells the listener to show the context menu for the given range and selected text. 27 | * The bounds parameter contains a json string representing the selection bounds in the form 28 | * { 'left': leftPoint, 'top': topPoint, 'right': rightPoint, 'bottom': bottomPoint } 29 | * @param range 30 | * @param text 31 | * @param handleBounds 32 | * @param isReallyChanged 33 | */ 34 | void selectionChanged(String range, String text, String handleBounds, boolean isReallyChanged); 35 | 36 | void setContentWidth(float contentWidth); 37 | } -------------------------------------------------------------------------------- /library/src/com/bossturban/webviewmarker/TextSelectionController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 - 2014 Brandon Tate, bossturbo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bossturban.webviewmarker; 18 | 19 | import android.webkit.JavascriptInterface; 20 | 21 | public class TextSelectionController { 22 | public static final String TAG = "TextSelectionController"; 23 | public static final String INTERFACE_NAME = "TextSelection"; 24 | 25 | private TextSelectionControlListener mListener; 26 | 27 | public TextSelectionController(TextSelectionControlListener listener) { 28 | mListener = listener; 29 | } 30 | 31 | @JavascriptInterface 32 | public void jsError(String error) { 33 | if (mListener != null) { 34 | mListener.jsError(error); 35 | } 36 | } 37 | @JavascriptInterface 38 | public void jsLog(String message) { 39 | if (mListener != null) { 40 | mListener.jsLog(message); 41 | } 42 | } 43 | @JavascriptInterface 44 | public void startSelectionMode() { 45 | if (mListener != null) { 46 | mListener.startSelectionMode(); 47 | } 48 | } 49 | @JavascriptInterface 50 | public void endSelectionMode() { 51 | if (mListener != null) { 52 | mListener.endSelectionMode(); 53 | } 54 | } 55 | @JavascriptInterface 56 | public void selectionChanged(String range, String text, String handleBounds, boolean isReallyChanged) { 57 | if (mListener != null) { 58 | mListener.selectionChanged(range, text, handleBounds, isReallyChanged); 59 | } 60 | } 61 | @JavascriptInterface 62 | public void setContentWidth(float contentWidth) { 63 | if (mListener != null) { 64 | mListener.setContentWidth(contentWidth); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /library/src/com/bossturban/webviewmarker/TextSelectionSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 - 2014 Brandon Tate, bossturbo 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.bossturban.webviewmarker; 18 | 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | import com.blahti.drag.DragController; 23 | import com.blahti.drag.DragController.DragBehavior; 24 | import com.blahti.drag.DragLayer; 25 | import com.blahti.drag.DragListener; 26 | import com.blahti.drag.DragSource; 27 | import com.blahti.drag.MyAbsoluteLayout; 28 | import com.bossturban.webviewmarker.R; 29 | 30 | import android.annotation.SuppressLint; 31 | import android.app.Activity; 32 | import android.content.Context; 33 | import android.graphics.Rect; 34 | import android.os.Build; 35 | import android.util.DisplayMetrics; 36 | import android.util.Log; 37 | import android.view.Display; 38 | import android.view.LayoutInflater; 39 | import android.view.MotionEvent; 40 | import android.view.View; 41 | import android.view.View.OnTouchListener; 42 | import android.view.View.OnLongClickListener; 43 | import android.view.ViewGroup; 44 | import android.view.WindowManager; 45 | import android.webkit.WebSettings; 46 | import android.webkit.WebView; 47 | import android.widget.ImageView; 48 | 49 | import java.util.Locale; 50 | 51 | @SuppressLint("DefaultLocale") 52 | public class TextSelectionSupport implements TextSelectionControlListener, OnTouchListener, OnLongClickListener, DragListener { 53 | public interface SelectionListener { 54 | void startSelection(); 55 | void selectionChanged(String text); 56 | void endSelection(); 57 | } 58 | 59 | private enum HandleType { 60 | START, 61 | END, 62 | UNKNOWN 63 | } 64 | private static final String TAG = "SelectionSupport"; 65 | private static final float CENTERING_SHORTER_MARGIN_RATIO = 12.0f / 48.0f; 66 | private static final int JACK_UP_PADDING = 2; 67 | private static final int SCROLLING_THRESHOLD = 10; 68 | 69 | private Activity mActivity; 70 | private WebView mWebView; 71 | private SelectionListener mSelectionListener; 72 | private DragLayer mSelectionDragLayer; 73 | private DragController mDragController; 74 | private ImageView mStartSelectionHandle; 75 | private ImageView mEndSelectionHandle; 76 | private Rect mSelectionBounds = null; 77 | private final Rect mSelectionBoundsTemp = new Rect(); 78 | private TextSelectionController mSelectionController = null; 79 | private int mContentWidth = 0; 80 | private HandleType mLastTouchedSelectionHandle = HandleType.UNKNOWN; 81 | private boolean mScrolling = false; 82 | private float mScrollDiffY = 0; 83 | private float mLastTouchY = 0; 84 | private float mScrollDiffX = 0; 85 | private float mLastTouchX = 0; 86 | private float mScale = 1.0f; 87 | 88 | private Runnable mStartSelectionModeHandler = new Runnable() { 89 | public void run() { 90 | if (mSelectionBounds != null) { 91 | mWebView.addView(mSelectionDragLayer); 92 | drawSelectionHandles(); 93 | final int contentHeight = (int)Math.ceil(getDensityDependentValue(mWebView.getContentHeight(), mActivity)); 94 | final int contentWidth = mWebView.getWidth(); 95 | ViewGroup.LayoutParams layerParams = mSelectionDragLayer.getLayoutParams(); 96 | layerParams.height = contentHeight; 97 | layerParams.width = Math.max(contentWidth, mContentWidth); 98 | mSelectionDragLayer.setLayoutParams(layerParams); 99 | if (mSelectionListener != null) { 100 | mSelectionListener.startSelection(); 101 | } 102 | } 103 | } 104 | }; 105 | private Runnable endSelectionModeHandler = new Runnable(){ 106 | public void run() { 107 | mWebView.removeView(mSelectionDragLayer); 108 | mSelectionBounds = null; 109 | mLastTouchedSelectionHandle = HandleType.UNKNOWN; 110 | mWebView.loadUrl("javascript: android.selection.clearSelection();"); 111 | if (mSelectionListener != null) { 112 | mSelectionListener.endSelection(); 113 | } 114 | } 115 | }; 116 | 117 | private TextSelectionSupport(Activity activity, WebView webview) { 118 | mActivity = activity; 119 | mWebView = webview; 120 | } 121 | public static TextSelectionSupport support(Activity activity, WebView webview) { 122 | final TextSelectionSupport selectionSupport = new TextSelectionSupport(activity, webview); 123 | selectionSupport.setup(); 124 | return selectionSupport; 125 | } 126 | 127 | public void onScaleChanged(float oldScale, float newScale) { 128 | mScale = newScale; 129 | } 130 | public void setSelectionListener(SelectionListener listener) { 131 | mSelectionListener = listener; 132 | } 133 | 134 | // 135 | // Interfaces of TextSelectionControlListener 136 | // 137 | @Override 138 | public void jsError(String error) { 139 | Log.e(TAG, "JSError: " + error); 140 | } 141 | @Override 142 | public void jsLog(String message) { 143 | Log.d(TAG, "JSLog: " + message); 144 | } 145 | @Override 146 | public void startSelectionMode() { 147 | mActivity.runOnUiThread(mStartSelectionModeHandler); 148 | } 149 | @Override 150 | public void endSelectionMode() { 151 | mActivity.runOnUiThread(endSelectionModeHandler); 152 | } 153 | @Override 154 | public void setContentWidth(float contentWidth){ 155 | mContentWidth = (int)getDensityDependentValue(contentWidth, mActivity); 156 | } 157 | @Override 158 | public void selectionChanged(String range, String text, String handleBounds, boolean isReallyChanged){ 159 | final Context ctx = mActivity; 160 | try { 161 | final JSONObject selectionBoundsObject = new JSONObject(handleBounds); 162 | final float scale = getDensityIndependentValue(mScale, ctx); 163 | Rect rect = mSelectionBoundsTemp; 164 | rect.left = (int)(getDensityDependentValue(selectionBoundsObject.getInt("left"), ctx) * scale); 165 | rect.top = (int)(getDensityDependentValue(selectionBoundsObject.getInt("top"), ctx) * scale); 166 | rect.right = (int)(getDensityDependentValue(selectionBoundsObject.getInt("right"), ctx) * scale); 167 | rect.bottom = (int)(getDensityDependentValue(selectionBoundsObject.getInt("bottom"), ctx) * scale); 168 | mSelectionBounds = rect; 169 | if (!isInSelectionMode()){ 170 | startSelectionMode(); 171 | } 172 | drawSelectionHandles(); 173 | if (mSelectionListener != null && isReallyChanged) { 174 | mSelectionListener.selectionChanged(text); 175 | } 176 | } 177 | catch (JSONException e) { 178 | e.printStackTrace(); 179 | } 180 | } 181 | 182 | // 183 | // Interface of OnTouchListener 184 | // 185 | @Override 186 | public boolean onTouch(View v, MotionEvent event) { 187 | final Context ctx = mActivity; 188 | float xPoint = getDensityIndependentValue(event.getX(), ctx) / getDensityIndependentValue(mScale, ctx); 189 | float yPoint = getDensityIndependentValue(event.getY(), ctx) / getDensityIndependentValue(mScale, ctx); 190 | 191 | switch (event.getAction()) { 192 | case MotionEvent.ACTION_DOWN: 193 | // Essential to add Locale.US parameter to String.format, else does not work on systems 194 | // with default locale different, with other floating point notations, e.g. comma instead 195 | // of decimal point. 196 | final String startTouchUrl = String.format(Locale.US, "javascript:android.selection.startTouch(%f, %f);", xPoint, yPoint); 197 | mLastTouchX = xPoint; 198 | mLastTouchY = yPoint; 199 | mWebView.loadUrl(startTouchUrl); 200 | break; 201 | case MotionEvent.ACTION_UP: 202 | if (!mScrolling) { 203 | endSelectionMode(); 204 | // 205 | // Fixes 4.4 double selection 206 | // See: http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4 207 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 208 | return false; 209 | } 210 | } 211 | mScrollDiffX = 0; 212 | mScrollDiffY = 0; 213 | mScrolling = false; 214 | // 215 | // Fixes 4.4 double selection 216 | // See: http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4 217 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isInSelectionMode()) { 218 | return true; 219 | } 220 | break; 221 | case MotionEvent.ACTION_MOVE: 222 | mScrollDiffX += (xPoint - mLastTouchX); 223 | mScrollDiffY += (yPoint - mLastTouchY); 224 | mLastTouchX = xPoint; 225 | mLastTouchY = yPoint; 226 | if (Math.abs(mScrollDiffX) > SCROLLING_THRESHOLD || Math.abs(mScrollDiffY) > SCROLLING_THRESHOLD) { 227 | mScrolling = true; 228 | } 229 | break; 230 | } 231 | return false; 232 | } 233 | 234 | // 235 | // Interface of OnLongClickListener 236 | // 237 | @Override 238 | public boolean onLongClick(View v){ 239 | if (!isInSelectionMode()) { 240 | mWebView.loadUrl("javascript:android.selection.longTouch();"); 241 | mScrolling = true; 242 | } 243 | return true; 244 | } 245 | 246 | // 247 | // Interface of DragListener 248 | // 249 | @Override 250 | public void onDragStart(DragSource source, Object info, DragBehavior dragBehavior) { 251 | } 252 | @Override 253 | public void onDragEnd() { 254 | mActivity.runOnUiThread(new Runnable() { 255 | @Override 256 | public void run() { 257 | MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams)mStartSelectionHandle.getLayoutParams(); 258 | MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams)mEndSelectionHandle.getLayoutParams(); 259 | final Context ctx = mActivity; 260 | final float scale = getDensityIndependentValue(mScale, ctx); 261 | float startX = startHandleParams.x - mWebView.getScrollX() + mStartSelectionHandle.getWidth() * (1 - CENTERING_SHORTER_MARGIN_RATIO); 262 | float startY = startHandleParams.y - mWebView.getScrollY() - JACK_UP_PADDING; 263 | float endX = endHandleParams.x - mWebView.getScrollX() + mEndSelectionHandle.getWidth() * CENTERING_SHORTER_MARGIN_RATIO; 264 | float endY = endHandleParams.y - mWebView.getScrollY() - JACK_UP_PADDING; 265 | startX = getDensityIndependentValue(startX, ctx) / scale; 266 | startY = getDensityIndependentValue(startY, ctx) / scale; 267 | endX = getDensityIndependentValue(endX, ctx) / scale; 268 | endY = getDensityIndependentValue(endY, ctx) / scale; 269 | if (mLastTouchedSelectionHandle == HandleType.START && startX > 0 && startY > 0){ 270 | String saveStartString = String.format(Locale.US, "javascript: android.selection.setStartPos(%f, %f);", startX, startY); 271 | mWebView.loadUrl(saveStartString); 272 | } 273 | else if (mLastTouchedSelectionHandle == HandleType.END && endX > 0 && endY > 0){ 274 | String saveEndString = String.format(Locale.US, "javascript: android.selection.setEndPos(%f, %f);", endX, endY); 275 | mWebView.loadUrl(saveEndString); 276 | } 277 | else { 278 | mWebView.loadUrl("javascript: android.selection.restoreStartEndPos();"); 279 | } 280 | } 281 | }); 282 | } 283 | 284 | @SuppressLint("SetJavaScriptEnabled") 285 | private void setup(){ 286 | mScale = mActivity.getResources().getDisplayMetrics().density; 287 | mWebView.setOnLongClickListener(this); 288 | mWebView.setOnTouchListener(this); 289 | final WebSettings settings = mWebView.getSettings(); 290 | settings.setJavaScriptEnabled(true); 291 | settings.setJavaScriptCanOpenWindowsAutomatically(true); 292 | mSelectionController = new TextSelectionController(this); 293 | mWebView.addJavascriptInterface(mSelectionController, TextSelectionController.INTERFACE_NAME); 294 | createSelectionLayer(mActivity); 295 | } 296 | private void createSelectionLayer(Context context){ 297 | final LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 298 | mSelectionDragLayer = (DragLayer)inflater.inflate(R.layout.selection_drag_layer, null); 299 | mDragController = new DragController(context); 300 | mDragController.setDragListener(this); 301 | mDragController.addDropTarget(mSelectionDragLayer); 302 | mSelectionDragLayer.setDragController(mDragController); 303 | mStartSelectionHandle = (ImageView)mSelectionDragLayer.findViewById(R.id.startHandle); 304 | mStartSelectionHandle.setTag(HandleType.START); 305 | mEndSelectionHandle = (ImageView)mSelectionDragLayer.findViewById(R.id.endHandle); 306 | mEndSelectionHandle.setTag(HandleType.END); 307 | final OnTouchListener handleTouchListener = new OnTouchListener(){ 308 | @Override 309 | public boolean onTouch(View v, MotionEvent event) { 310 | boolean handledHere = false; 311 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 312 | handledHere = startDrag(v); 313 | mLastTouchedSelectionHandle = (HandleType)v.getTag(); 314 | } 315 | return handledHere; 316 | } 317 | }; 318 | mStartSelectionHandle.setOnTouchListener(handleTouchListener); 319 | mEndSelectionHandle.setOnTouchListener(handleTouchListener); 320 | } 321 | private void drawSelectionHandles(){ 322 | mActivity.runOnUiThread(drawSelectionHandlesHandler); 323 | } 324 | private Runnable drawSelectionHandlesHandler = new Runnable(){ 325 | public void run() { 326 | MyAbsoluteLayout.LayoutParams startParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams)mStartSelectionHandle.getLayoutParams(); 327 | final int startWidth = mStartSelectionHandle.getDrawable().getIntrinsicWidth(); 328 | startParams.x = (int)(mSelectionBounds.left - startWidth * (1.0f - CENTERING_SHORTER_MARGIN_RATIO)); 329 | startParams.y = (int)(mSelectionBounds.top); 330 | final int startMinLeft = -(int)(startWidth * (1 - CENTERING_SHORTER_MARGIN_RATIO)); 331 | startParams.x = (startParams.x < startMinLeft) ? startMinLeft : startParams.x; 332 | startParams.y = (startParams.y < 0) ? 0 : startParams.y; 333 | mStartSelectionHandle.setLayoutParams(startParams); 334 | 335 | MyAbsoluteLayout.LayoutParams endParams = (com.blahti.drag.MyAbsoluteLayout.LayoutParams)mEndSelectionHandle.getLayoutParams(); 336 | final int endWidth = mEndSelectionHandle.getDrawable().getIntrinsicWidth(); 337 | endParams.x = (int) (mSelectionBounds.right - endWidth * CENTERING_SHORTER_MARGIN_RATIO); 338 | endParams.y = (int) (mSelectionBounds.bottom); 339 | final int endMinLeft = -(int)(endWidth * (1- CENTERING_SHORTER_MARGIN_RATIO)); 340 | endParams.x = (endParams.x < endMinLeft) ? endMinLeft : endParams.x; 341 | endParams.y = (endParams.y < 0) ? 0 : endParams.y; 342 | mEndSelectionHandle.setLayoutParams(endParams); 343 | } 344 | }; 345 | 346 | private boolean isInSelectionMode(){ 347 | return this.mSelectionDragLayer.getParent() != null; 348 | } 349 | private boolean startDrag(View v) { 350 | Object dragInfo = v; 351 | mDragController.startDrag(v, mSelectionDragLayer, dragInfo, DragBehavior.MOVE); 352 | return true; 353 | } 354 | 355 | private float getDensityDependentValue(float val, Context ctx){ 356 | Display display = ((WindowManager)ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 357 | DisplayMetrics metrics = new DisplayMetrics(); 358 | display.getMetrics(metrics); 359 | return val * (metrics.densityDpi / 160f); 360 | } 361 | private float getDensityIndependentValue(float val, Context ctx){ 362 | Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 363 | DisplayMetrics metrics = new DisplayMetrics(); 364 | display.getMetrics(metrics); 365 | return val / (metrics.densityDpi / 160f); 366 | } 367 | } -------------------------------------------------------------------------------- /samples/demos/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/demos/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | -------------------------------------------------------------------------------- /samples/demos/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebViewMarkerDemo 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/demos/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/demos/assets/android.selection.js: -------------------------------------------------------------------------------- 1 | // Namespace 2 | var android = {}; 3 | android.selection = {}; 4 | 5 | android.selection.selectionStartRange = null; 6 | android.selection.selectionEndRange = null; 7 | 8 | /** The last point touched by the user. { 'x': xPoint, 'y': yPoint } */ 9 | android.selection.lastTouchPoint = null; 10 | 11 | 12 | /** 13 | * Starts the touch and saves the given x and y coordinates as last touch point 14 | */ 15 | android.selection.startTouch = function(x, y) { 16 | android.selection.lastTouchPoint = {'x': x, 'y': y}; 17 | }; 18 | 19 | /** 20 | * Checks to see if there is a selection. 21 | * 22 | * @return boolean 23 | */ 24 | android.selection.hasSelection = function() { 25 | return window.getSelection().toString().length > 0; 26 | }; 27 | 28 | /** 29 | * Clears the current selection. 30 | */ 31 | android.selection.clearSelection = function() { 32 | try { 33 | // if current selection clear it. 34 | var sel = window.getSelection(); 35 | sel.removeAllRanges(); 36 | } 37 | catch (e) { 38 | window.TextSelection.jsError(e); 39 | } 40 | }; 41 | 42 | /** 43 | * Handles the long touch action by selecting the last touched element. 44 | */ 45 | android.selection.longTouch = function() { 46 | try { 47 | android.selection.clearSelection(); 48 | var sel = window.getSelection(); 49 | var range = document.caretRangeFromPoint(android.selection.lastTouchPoint.x, android.selection.lastTouchPoint.y); 50 | range.expand("word"); 51 | var text = range.toString(); 52 | if (text.length == 1) { 53 | var baseKind = jpntext.kind(text); 54 | if (baseKind != jpntext.KIND['ascii']) { 55 | try { 56 | do { 57 | range.setEnd(range.endContainer, range.endOffset + 1); 58 | text = range.toString(); 59 | var kind = jpntext.kind(text); 60 | } while (baseKind == kind); 61 | range.setEnd(range.endContainer, range.endOffset - 1); 62 | } 63 | catch (e) { 64 | } 65 | try { 66 | do { 67 | range.setStart(range.startContainer, range.startOffset - 1); 68 | text = range.toString(); 69 | var kind = jpntext.kind(text); 70 | } while (baseKind == kind); 71 | range.setStart(range.startContainer, range.startOffset + 1); 72 | } 73 | catch (e) { 74 | } 75 | } 76 | } 77 | if (text.length > 0) { 78 | sel.addRange(range); 79 | android.selection.saveSelectionStart(); 80 | android.selection.saveSelectionEnd(); 81 | android.selection.selectionChanged(true); 82 | } 83 | } 84 | catch (err) { 85 | window.TextSelection.jsError(err); 86 | } 87 | }; 88 | 89 | /** 90 | * Tells the app to show the context menu. 91 | */ 92 | android.selection.selectionChanged = function(isReallyChanged) { 93 | try { 94 | var sel = window.getSelection(); 95 | if (!sel) { 96 | return; 97 | } 98 | var range = sel.getRangeAt(0); 99 | 100 | // Add spans to the selection to get page offsets 101 | var selectionStart = $(""); 102 | var selectionEnd = $(""); 103 | 104 | var startRange = document.createRange(); 105 | startRange.setStart(range.startContainer, range.startOffset); 106 | startRange.insertNode(selectionStart[0]); 107 | 108 | var endRange = document.createRange(); 109 | endRange.setStart(range.endContainer, range.endOffset); 110 | endRange.insertNode(selectionEnd[0]); 111 | 112 | var handleBounds = "{'left': " + (selectionStart.offset().left) + ", "; 113 | handleBounds += "'top': " + (selectionStart.offset().top + selectionStart.height()) + ", "; 114 | handleBounds += "'right': " + (selectionEnd.offset().left) + ", "; 115 | handleBounds += "'bottom': " + (selectionEnd.offset().top + selectionEnd.height()) + "}"; 116 | 117 | // Pull the spans 118 | selectionStart.remove(); 119 | selectionEnd.remove(); 120 | 121 | // Reset range 122 | sel.removeAllRanges(); 123 | sel.addRange(range); 124 | 125 | // Rangy 126 | var rangyRange = android.selection.getRange(); 127 | 128 | // Text to send to the selection 129 | var text = window.getSelection().toString(); 130 | 131 | // Set the content width 132 | window.TextSelection.setContentWidth(document.body.clientWidth); 133 | 134 | // Tell the interface that the selection changed 135 | window.TextSelection.selectionChanged(rangyRange, text, handleBounds, isReallyChanged); 136 | } 137 | catch (e) { 138 | window.TextSelection.jsError(e); 139 | } 140 | }; 141 | 142 | android.selection.getRange = function() { 143 | var serializedRangeSelected = rangy.serializeSelection(); 144 | var serializerModule = rangy.modules.Serializer; 145 | if (serializedRangeSelected != '') { 146 | if (rangy.supported && serializerModule && serializerModule.supported) { 147 | var beginingCurly = serializedRangeSelected.indexOf("{"); 148 | serializedRangeSelected = serializedRangeSelected.substring(0, beginingCurly); 149 | return serializedRangeSelected; 150 | } 151 | } 152 | } 153 | 154 | /** 155 | * Returns the last touch point as a readable string. 156 | */ 157 | android.selection.lastTouchPointString = function(){ 158 | if (android.selection.lastTouchPoint == null) 159 | return "undefined"; 160 | return "{" + android.selection.lastTouchPoint.x + "," + android.selection.lastTouchPoint.y + "}"; 161 | }; 162 | 163 | android.selection.saveSelectionStart = function(){ 164 | try { 165 | // Save the starting point of the selection 166 | var sel = window.getSelection(); 167 | var range = sel.getRangeAt(0); 168 | var saveRange = document.createRange(); 169 | saveRange.setStart(range.startContainer, range.startOffset); 170 | android.selection.selectionStartRange = saveRange; 171 | } 172 | catch (e) { 173 | window.TextSelection.jsError(e); 174 | } 175 | }; 176 | 177 | android.selection.saveSelectionEnd = function(){ 178 | try { 179 | // Save the end point of the selection 180 | var sel = window.getSelection(); 181 | var range = sel.getRangeAt(0); 182 | var saveRange = document.createRange(); 183 | saveRange.setStart(range.endContainer, range.endOffset); 184 | android.selection.selectionEndRange = saveRange; 185 | } 186 | catch (e) { 187 | window.TextSelection.jsError(e); 188 | } 189 | }; 190 | 191 | /** 192 | * Sets the last caret position for the start handle. 193 | */ 194 | android.selection.setStartPos = function(x, y){ 195 | try { 196 | android.selection.selectBetweenHandles(document.caretRangeFromPoint(x, y), android.selection.selectionEndRange); 197 | } 198 | catch (e) { 199 | window.TextSelection.jsError(e); 200 | } 201 | }; 202 | 203 | /** 204 | * Sets the last caret position for the end handle. 205 | */ 206 | android.selection.setEndPos = function(x, y){ 207 | try { 208 | android.selection.selectBetweenHandles(android.selection.selectionStartRange, document.caretRangeFromPoint(x, y)); 209 | } 210 | catch (e) { 211 | window.TextSelection.jsError(e); 212 | } 213 | }; 214 | 215 | android.selection.restoreStartEndPos = function() { 216 | try { 217 | android.selection.selectBetweenHandles(android.selection.selectionEndRange, android.selection.selectionStartRange); 218 | } 219 | catch (e) { 220 | window.TextSelection.jsError(e); 221 | } 222 | }; 223 | 224 | /** 225 | * Selects all content between the two handles 226 | */ 227 | android.selection.selectBetweenHandles = function(startCaret, endCaret) { 228 | try { 229 | if (startCaret && endCaret) { 230 | var rightOrder = startCaret.compareBoundaryPoints(Range.START_TO_END, endCaret) <= 0; 231 | if (rightOrder) { 232 | android.selection.selectionStartRange = startCaret; 233 | android.selection.selectionEndRange = endCaret; 234 | } 235 | else { 236 | startCaret = android.selection.selectionStartRange; 237 | endCaret = android.selection.selectionEndRange; 238 | } 239 | var range = document.createRange(); 240 | range.setStart(startCaret.startContainer, startCaret.startOffset); 241 | range.setEnd(endCaret.startContainer, endCaret.startOffset); 242 | android.selection.clearSelection(); 243 | var selection = window.getSelection(); 244 | selection.addRange(range); 245 | android.selection.selectionChanged(rightOrder); 246 | } 247 | else { 248 | android.selection.selectionStartRange = startCaret; 249 | android.selection.selectionEndRange = endCaret; 250 | } 251 | } 252 | catch (e) { 253 | window.TextSelection.jsError(e); 254 | } 255 | }; 256 | -------------------------------------------------------------------------------- /samples/demos/assets/content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras pellentesque, dolor nec luctus ullamcorper, massa quam interdum metus, ac ultricies mauris risus nec purus. Fusce et nunc mi, ut consequat velit. Cras orci sapien, tincidunt sed iaculis ac, commodo sed neque. Sed gravida, quam id imperdiet venenatis, odio erat faucibus nisi, sed imperdiet velit nulla nec risus. Duis eget vehicula nibh. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec tincidunt, augue vitae feugiat cursus, nunc nunc volutpat ligula, ac molestie purus lectus sit amet mauris. Nunc nec felis tortor, a dignissim eros. Vivamus diam mauris, scelerisque molestie bibendum ut, dignissim quis neque. Aliquam ac turpis mi. Sed pretium interdum orci, sed semper lorem viverra id. Vivamus diam eros, convallis sit amet facilisis in, facilisis eget nibh. Aenean porttitor, neque nec ultrices laoreet, dolor metus cursus purus, non auctor lectus urna a enim. In nec lectus nunc, ac commodo arcu. Proin bibendum ligula non mi bibendum porta. Nam ac metus a magna tristique euismod eget vitae purus. 16 |

17 |

18 | Vestibulum tincidunt, lectus at bibendum commodo, lectus velit commodo libero, et fermentum odio arcu sit amet dui. Quisque feugiat augue in erat scelerisque non mollis arcu facilisis. Nunc a dui sapien. Quisque quis nisi eu velit tincidunt placerat. Nunc vitae eros erat. Duis posuere diam ut orci adipiscing a ullamcorper est sollicitudin. Cras ipsum dolor, commodo viverra aliquet vel, pulvinar at sapien. Suspendisse eget justo et neque bibendum tempor. Morbi aliquet enim id arcu convallis cursus. Curabitur pellentesque condimentum dolor non sagittis. Ut tempor, erat in ullamcorper porta, purus erat tincidunt felis, ac auctor mauris libero nec purus. Praesent nisl justo, rutrum at rhoncus at, accumsan at mi. Fusce hendrerit imperdiet nulla a accumsan. Maecenas ut mi ac libero bibendum ullamcorper. 19 |

20 |

21 | Nam ac est nunc. Suspendisse faucibus dictum lacus, sed tincidunt erat laoreet id. Cras leo dui, sodales vitae blandit quis, placerat in arcu. Nunc semper odio id dolor bibendum vel euismod sapien malesuada. Vestibulum metus purus, consequat sed varius ornare, feugiat ac lectus. Suspendisse consectetur ipsum a enim aliquet a pellentesque ipsum condimentum. Etiam molestie, augue id consectetur bibendum, tellus ante vestibulum tellus, vitae aliquam turpis ipsum et elit. Aenean ipsum ante, eleifend in mollis at, molestie ac augue. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dui justo, consequat semper mollis sit amet, scelerisque vitae enim. Nullam nec odio et tortor sodales posuere. Mauris urna lorem, suscipit eu venenatis eu, consectetur ut purus. Vestibulum pharetra interdum convallis. Donec ac leo felis. 22 |

23 |

24 | Mauris vulputate magna eget ligula fermentum eu molestie justo vulputate. Pellentesque condimentum, sem a sollicitudin luctus, lorem nunc dictum risus, a tincidunt tortor metus nec risus. Nunc ultricies consectetur accumsan. Etiam placerat aliquam tortor id lacinia. Vestibulum quis sem non urna venenatis condimentum. Vestibulum tempor sapien quam, et vulputate elit. Sed convallis mauris sed turpis elementum vitae pretium est tempus. Praesent congue viverra pharetra. Etiam accumsan congue sapien eu molestie. 25 |

26 |

27 | Maecenas laoreet egestas tellus vestibulum gravida. Nulla rutrum dui at purus viverra et pretium diam fringilla. Sed nibh tortor, interdum ac egestas in, aliquam eu erat. Nunc convallis est posuere ipsum blandit luctus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quam eros, hendrerit quis iaculis ut, rhoncus vel elit. Aenean sed ante et urna semper eleifend. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi mollis lacus eu nulla vestibulum consectetur lobortis risus pretium. 28 |

29 |

30 | Mauris scelerisque imperdiet venenatis. Suspendisse bibendum ullamcorper massa, vitae aliquam ligula sagittis at. Nullam rutrum justo nec tellus egestas eleifend. Suspendisse euismod quam in neque laoreet aliquam. Nunc non massa quis magna gravida ornare sed vitae tortor. Donec fringilla euismod accumsan. Mauris condimentum libero eget nisi sodales fringilla. 31 |

32 |

33 | Suspendisse eget tellus nibh, eu accumsan nibh. Aenean a metus ut leo dapibus lacinia. Vestibulum vitae ante nec urna aliquet posuere sollicitudin vitae mauris. Pellentesque dapibus dapibus nisi, fringilla placerat metus aliquet id. Mauris imperdiet ornare tellus sed ornare. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus elementum fringilla molestie. Cras ac enim at libero condimentum eleifend. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; 34 |

35 |

36 | Maecenas vel hendrerit massa. Maecenas quis augue turpis. Fusce scelerisque gravida nisl vitae auctor. Donec porta, diam id fringilla convallis, risus elit ultrices enim, nec auctor mi justo ut urna. Integer consectetur, sem sed molestie adipiscing, ligula lectus convallis nisl, eget pretium ante nulla non sem. Nulla magna metus, ornare at facilisis sit amet, ultrices id erat. Mauris gravida risus ac augue vulputate imperdiet porta tortor fringilla. Pellentesque cursus aliquam velit at mattis. Aliquam molestie fringilla urna, eget convallis sem hendrerit in. Cras pretium, nunc et aliquam ullamcorper, justo augue cursus turpis, vitae volutpat urna mi sit amet nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 37 |

38 |

39 | Donec iaculis aliquam nulla sodales sodales. Nunc quis egestas ipsum. Suspendisse accumsan tortor eu lacus sodales eu venenatis lectus varius. Vestibulum nisl mi, vulputate quis pulvinar eu, malesuada vitae dui. Ut eget nulla ipsum. Donec in risus laoreet arcu venenatis aliquam. Curabitur orci augue, pulvinar quis sollicitudin et, laoreet a turpis. Donec a nunc id elit volutpat euismod sed at lorem. 40 |

41 |

42 | Phasellus arcu augue, rhoncus nec luctus quis, pharetra sit amet ante. Phasellus sed dui sit amet lacus auctor varius ac a arcu. Nullam eu congue ligula. Duis pretium nisi et tellus faucibus commodo. Vivamus dapibus imperdiet condimentum. Fusce at velit arcu, ac imperdiet mauris. Donec sit amet metus libero. Integer ac enim elit. Nulla quis erat urna. Morbi lobortis ligula vel sem placerat porttitor. Vestibulum in velit nulla, in malesuada sapien. Pellentesque sed sapien urna. Maecenas sodales felis sed nisl dictum euismod. Cras vitae sagittis elit. 43 |

44 |

45 | あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。 46 |

47 |

48 | またそのなかでいっしょになったたくさんのひとたち、ファゼーロとロザーロ、羊飼のミーロや、顔の赤いこどもたち、地主のテーモ、山猫博士のボーガント・デストゥパーゴなど、いまこの暗い巨きな石の建物のなかで考えていると、みんなむかし風のなつかしい青い幻燈のように思われます。では、わたくしはいつかの小さなみだしをつけながら、しずかにあの年のイーハトーヴォの五月から十月までを書きつけましょう。 49 |

50 | 51 | -------------------------------------------------------------------------------- /samples/demos/assets/css/sample.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 5px; 4 | } 5 | p::selection { 6 | background-color: #98d9f1; 7 | } -------------------------------------------------------------------------------- /samples/demos/assets/jpntext.js: -------------------------------------------------------------------------------- 1 | var jpntext = (function() { 2 | var global = { 3 | KIND: { 4 | 'mix': 0, 5 | 'ascii': 1, 6 | 'hira': 2, 7 | 'kata': 3, 8 | 'cjk': 4 9 | }, 10 | kind: function(text) { 11 | var result; 12 | if (global.isAscii(text)) { 13 | result = 'ascii'; 14 | } 15 | else if (global.isHiragana(text)) { 16 | result = 'hira'; 17 | } 18 | else if (global.isKatakana(text)) { 19 | result = 'kata'; 20 | } 21 | else if (global.isKanji(text)) { 22 | result = 'cjk'; 23 | } 24 | else { 25 | result = 'mix'; 26 | } 27 | return global.KIND[result]; 28 | }, 29 | isAscii: function(text) { 30 | var re = /^[\u0000-\u00ff]+$/; 31 | return re.test(text); 32 | }, 33 | isKanji: function(text) { 34 | var re = /^([\u4e00-\u9fcf]|[\u3400-\u4dbf]|[\u20000-\u2a6df]|[\uf900-\ufadf])+$/; 35 | return re.test(text); 36 | }, 37 | isHiragana: function(text) { 38 | var re = /^[\u3040-\u309f]+$/; 39 | return re.test(text) 40 | }, 41 | isKatakana: function(text) { 42 | var re = /^[\u30a0-\u30ff]+$/; 43 | return re.test(text); 44 | } 45 | }; 46 | return global; 47 | })(); 48 | -------------------------------------------------------------------------------- /samples/demos/assets/rangy-serializer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Serializer module for Rangy. 3 | * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a 4 | * cookie or local storage and restore it on the user's next visit to the same page. 5 | * 6 | * Part of Rangy, a cross-browser JavaScript range and selection library 7 | * http://code.google.com/p/rangy/ 8 | * 9 | * Depends on Rangy core. 10 | * 11 | * Copyright 2012, Tim Down 12 | * Licensed under the MIT license. 13 | * Version: 1.2.3 14 | * Build date: 26 February 2012 15 | */ 16 | rangy.createModule("Serializer", function(api, module) { 17 | api.requireModules( ["WrappedSelection", "WrappedRange"] ); 18 | var UNDEF = "undefined"; 19 | 20 | // encodeURIComponent and decodeURIComponent are required for cookie handling 21 | if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) { 22 | module.fail("Global object is missing encodeURIComponent and/or decodeURIComponent method"); 23 | } 24 | 25 | // Checksum for checking whether range can be serialized 26 | var crc32 = (function() { 27 | function utf8encode(str) { 28 | var utf8CharCodes = []; 29 | 30 | for (var i = 0, len = str.length, c; i < len; ++i) { 31 | c = str.charCodeAt(i); 32 | if (c < 128) { 33 | utf8CharCodes.push(c); 34 | } else if (c < 2048) { 35 | utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128); 36 | } else { 37 | utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128); 38 | } 39 | } 40 | return utf8CharCodes; 41 | } 42 | 43 | var cachedCrcTable = null; 44 | 45 | function buildCRCTable() { 46 | var table = []; 47 | for (var i = 0, j, crc; i < 256; ++i) { 48 | crc = i; 49 | j = 8; 50 | while (j--) { 51 | if ((crc & 1) == 1) { 52 | crc = (crc >>> 1) ^ 0xEDB88320; 53 | } else { 54 | crc >>>= 1; 55 | } 56 | } 57 | table[i] = crc >>> 0; 58 | } 59 | return table; 60 | } 61 | 62 | function getCrcTable() { 63 | if (!cachedCrcTable) { 64 | cachedCrcTable = buildCRCTable(); 65 | } 66 | return cachedCrcTable; 67 | } 68 | 69 | return function(str) { 70 | var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable(); 71 | for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) { 72 | y = (crc ^ utf8CharCodes[i]) & 0xFF; 73 | crc = (crc >>> 8) ^ crcTable[y]; 74 | } 75 | return (crc ^ -1) >>> 0; 76 | }; 77 | })(); 78 | 79 | var dom = api.dom; 80 | 81 | function escapeTextForHtml(str) { 82 | return str.replace(//g, ">"); 83 | } 84 | 85 | function nodeToInfoString(node, infoParts) { 86 | infoParts = infoParts || []; 87 | var nodeType = node.nodeType, children = node.childNodes, childCount = children.length; 88 | var nodeInfo = [nodeType, node.nodeName, childCount].join(":"); 89 | var start = "", end = ""; 90 | switch (nodeType) { 91 | case 3: // Text node 92 | start = escapeTextForHtml(node.nodeValue); 93 | break; 94 | case 8: // Comment 95 | start = ""; 96 | break; 97 | default: 98 | start = "<" + nodeInfo + ">"; 99 | end = ""; 100 | break; 101 | } 102 | if (start) { 103 | infoParts.push(start); 104 | } 105 | for (var i = 0; i < childCount; ++i) { 106 | nodeToInfoString(children[i], infoParts); 107 | } 108 | if (end) { 109 | infoParts.push(end); 110 | } 111 | return infoParts; 112 | } 113 | 114 | // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all 115 | // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around 116 | // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's 117 | // innerHTML whenever the user changes an input within the element. 118 | function getElementChecksum(el) { 119 | var info = nodeToInfoString(el).join(""); 120 | return crc32(info).toString(16); 121 | } 122 | 123 | function serializePosition(node, offset, rootNode) { 124 | var pathBits = [], n = node; 125 | rootNode = rootNode || dom.getDocument(node).documentElement; 126 | while (n && n != rootNode) { 127 | pathBits.push(dom.getNodeIndex(n, true)); 128 | n = n.parentNode; 129 | } 130 | return pathBits.join("/") + ":" + offset; 131 | } 132 | 133 | function deserializePosition(serialized, rootNode, doc) { 134 | if (rootNode) { 135 | doc = doc || dom.getDocument(rootNode); 136 | } else { 137 | doc = doc || document; 138 | rootNode = doc.documentElement; 139 | } 140 | var bits = serialized.split(":"); 141 | var node = rootNode; 142 | var nodeIndices = bits[0] ? bits[0].split("/") : [], i = nodeIndices.length, nodeIndex; 143 | 144 | while (i--) { 145 | nodeIndex = parseInt(nodeIndices[i], 10); 146 | if (nodeIndex < node.childNodes.length) { 147 | node = node.childNodes[parseInt(nodeIndices[i], 10)]; 148 | } else { 149 | throw module.createError("deserializePosition failed: node " + dom.inspectNode(node) + 150 | " has no child with index " + nodeIndex + ", " + i); 151 | } 152 | } 153 | 154 | return new dom.DomPosition(node, parseInt(bits[1], 10)); 155 | } 156 | 157 | function serializeRange(range, omitChecksum, rootNode) { 158 | rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement; 159 | if (!dom.isAncestorOf(rootNode, range.commonAncestorContainer, true)) { 160 | throw new Error("serializeRange: range is not wholly contained within specified root node"); 161 | } 162 | var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," + 163 | serializePosition(range.endContainer, range.endOffset, rootNode); 164 | if (!omitChecksum) { 165 | serialized += "{" + getElementChecksum(rootNode) + "}"; 166 | } 167 | return serialized; 168 | } 169 | 170 | function deserializeRange(serialized, rootNode, doc) { 171 | if (rootNode) { 172 | doc = doc || dom.getDocument(rootNode); 173 | } else { 174 | doc = doc || document; 175 | rootNode = doc.documentElement; 176 | } 177 | var result = /^([^,]+),([^,\{]+)({([^}]+)})?$/.exec(serialized); 178 | var checksum = result[4], rootNodeChecksum = getElementChecksum(rootNode); 179 | if (checksum && checksum !== getElementChecksum(rootNode)) { 180 | throw new Error("deserializeRange: checksums of serialized range root node (" + checksum + 181 | ") and target root node (" + rootNodeChecksum + ") do not match"); 182 | } 183 | var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc); 184 | var range = api.createRange(doc); 185 | range.setStart(start.node, start.offset); 186 | range.setEnd(end.node, end.offset); 187 | return range; 188 | } 189 | 190 | function canDeserializeRange(serialized, rootNode, doc) { 191 | if (rootNode) { 192 | doc = doc || dom.getDocument(rootNode); 193 | } else { 194 | doc = doc || document; 195 | rootNode = doc.documentElement; 196 | } 197 | var result = /^([^,]+),([^,]+)({([^}]+)})?$/.exec(serialized); 198 | var checksum = result[3]; 199 | return !checksum || checksum === getElementChecksum(rootNode); 200 | } 201 | 202 | function serializeSelection(selection, omitChecksum, rootNode) { 203 | selection = selection || api.getSelection(); 204 | var ranges = selection.getAllRanges(), serializedRanges = []; 205 | for (var i = 0, len = ranges.length; i < len; ++i) { 206 | serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode); 207 | } 208 | return serializedRanges.join("|"); 209 | } 210 | 211 | function deserializeSelection(serialized, rootNode, win) { 212 | if (rootNode) { 213 | win = win || dom.getWindow(rootNode); 214 | } else { 215 | win = win || window; 216 | rootNode = win.document.documentElement; 217 | } 218 | var serializedRanges = serialized.split("|"); 219 | var sel = api.getSelection(win); 220 | var ranges = []; 221 | 222 | for (var i = 0, len = serializedRanges.length; i < len; ++i) { 223 | ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document); 224 | } 225 | sel.setRanges(ranges); 226 | 227 | return sel; 228 | } 229 | 230 | function canDeserializeSelection(serialized, rootNode, win) { 231 | var doc; 232 | if (rootNode) { 233 | doc = win ? win.document : dom.getDocument(rootNode); 234 | } else { 235 | win = win || window; 236 | rootNode = win.document.documentElement; 237 | } 238 | var serializedRanges = serialized.split("|"); 239 | 240 | for (var i = 0, len = serializedRanges.length; i < len; ++i) { 241 | if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) { 242 | return false; 243 | } 244 | } 245 | 246 | return true; 247 | } 248 | 249 | 250 | var cookieName = "rangySerializedSelection"; 251 | 252 | function getSerializedSelectionFromCookie(cookie) { 253 | var parts = cookie.split(/[;,]/); 254 | for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) { 255 | nameVal = parts[i].split("="); 256 | if (nameVal[0].replace(/^\s+/, "") == cookieName) { 257 | val = nameVal[1]; 258 | if (val) { 259 | return decodeURIComponent(val.replace(/\s+$/, "")); 260 | } 261 | } 262 | } 263 | return null; 264 | } 265 | 266 | function restoreSelectionFromCookie(win) { 267 | win = win || window; 268 | var serialized = getSerializedSelectionFromCookie(win.document.cookie); 269 | if (serialized) { 270 | deserializeSelection(serialized, win.doc) 271 | } 272 | } 273 | 274 | function saveSelectionCookie(win, props) { 275 | win = win || window; 276 | props = (typeof props == "object") ? props : {}; 277 | var expires = props.expires ? ";expires=" + props.expires.toUTCString() : ""; 278 | var path = props.path ? ";path=" + props.path : ""; 279 | var domain = props.domain ? ";domain=" + props.domain : ""; 280 | var secure = props.secure ? ";secure" : ""; 281 | var serialized = serializeSelection(api.getSelection(win)); 282 | win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure; 283 | } 284 | 285 | api.serializePosition = serializePosition; 286 | api.deserializePosition = deserializePosition; 287 | 288 | api.serializeRange = serializeRange; 289 | api.deserializeRange = deserializeRange; 290 | api.canDeserializeRange = canDeserializeRange; 291 | 292 | api.serializeSelection = serializeSelection; 293 | api.deserializeSelection = deserializeSelection; 294 | api.canDeserializeSelection = canDeserializeSelection; 295 | 296 | api.restoreSelectionFromCookie = restoreSelectionFromCookie; 297 | api.saveSelectionCookie = saveSelectionCookie; 298 | 299 | api.getElementChecksum = getElementChecksum; 300 | }); 301 | -------------------------------------------------------------------------------- /samples/demos/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | 22 | -optimizationpasses 2 23 | 24 | -keepattributes JavascriptInterface 25 | 26 | -keepclassmembers class com.bossturban.webviewmarker.TextSelectionController { 27 | public *; 28 | } 29 | 30 | -keepclassmembers class **.R$* { 31 | public static ; 32 | } -------------------------------------------------------------------------------- /samples/demos/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-17 12 | android.library.reference.1=../../library 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/demos/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/samples/demos/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/demos/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/samples/demos/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/demos/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/samples/demos/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/demos/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naoak/WebViewMarker/b50e82d2b6fa530b10fefd3762a72448a3f5d251/samples/demos/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/demos/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /samples/demos/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | WebViewMarkerDemos 4 | 5 | -------------------------------------------------------------------------------- /samples/demos/src/com/bossturban/webviewmarker/sample/demos/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bossturban.webviewmarker.sample.demos; 2 | 3 | import com.bossturban.webviewmarker.TextSelectionSupport; 4 | import com.bossturban.webviewmarker.sample.demos.R; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.webkit.WebView; 9 | import android.webkit.WebViewClient; 10 | import android.widget.Toast; 11 | 12 | public class MainActivity extends Activity { 13 | private WebView mWebView; 14 | private TextSelectionSupport mTextSelectionSupport; 15 | 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.main); 20 | mWebView = (WebView)findViewById(R.id.webView); 21 | mTextSelectionSupport = TextSelectionSupport.support(this, mWebView); 22 | mTextSelectionSupport.setSelectionListener(new TextSelectionSupport.SelectionListener() { 23 | @Override 24 | public void startSelection() { 25 | } 26 | @Override 27 | public void selectionChanged(String text) { 28 | Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); 29 | } 30 | @Override 31 | public void endSelection() { 32 | } 33 | }); 34 | mWebView.setWebViewClient(new WebViewClient() { 35 | public void onScaleChanged(WebView view, float oldScale, float newScale) { 36 | mTextSelectionSupport.onScaleChanged(oldScale, newScale); 37 | } 38 | }); 39 | mWebView.loadUrl("file:///android_asset/content.html"); 40 | } 41 | } 42 | --------------------------------------------------------------------------------