content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.text.method;
  
  import android.text.Layout;
  import android.text.Layout.Alignment;
  import android.text.NoCopySpan;
  import android.text.Spannable;
  import android.view.KeyEvent;
  import android.view.MotionEvent;
  import android.view.ViewConfiguration;
  import android.widget.TextView;
  
  public class Touch {
      private Touch() { }
  
      /**
       * Scrolls the specified widget to the specified coordinates, except
       * constrains the X scrolling position to the horizontal regions of
       * the text that will be visible after scrolling to the specified
       * Y position.
       */
      public static void scrollTo(TextView widget, Layout layout, int x, int y) {
          final int horizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
          final int availableWidth = widget.getWidth() - horizontalPadding;
  
          final int top = layout.getLineForVertical(y);
          Alignment a = layout.getParagraphAlignment(top);
          boolean ltr = layout.getParagraphDirection(top) > 0;
  
          int left, right;
          if (widget.getHorizontallyScrolling()) {
              final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
              final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding);
  
              left = Integer.MAX_VALUE;
              right = 0;
  
              for (int i = top; i <= bottom; i++) {
                  left = (int) Math.min(left, layout.getLineLeft(i));
                  right = (int) Math.max(right, layout.getLineRight(i));
              }
          } else {
              left = 0;
              right = availableWidth;
          }
  
          final int actualWidth = right - left;
  
          if (actualWidth < availableWidth) {
              if (a == Alignment.ALIGN_CENTER) {
                  x = left - ((availableWidth - actualWidth) / 2);
              } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) ||
                         (!ltr && (a == Alignment.ALIGN_NORMAL)) ||
                         (a == Alignment.ALIGN_RIGHT)) {
                  // align_opposite does NOT mean align_right, we need the paragraph
                  // direction to resolve it to left or right
                  x = left - (availableWidth - actualWidth);
              } else {
                  x = left;
              }
          } else {
              x = Math.min(x, right - availableWidth);
              x = Math.max(x, left);
          }
  
          widget.scrollTo(x, y);
      }
  
      /**
       * Handles touch events for dragging.  You may want to do other actions
       * like moving the cursor on touch as well.
       */
      public static boolean onTouchEvent(TextView widget, Spannable buffer,
                                         MotionEvent event) {
          DragState[] ds;
  
          switch (event.getActionMasked()) {
          case MotionEvent.ACTION_DOWN:
              ds = buffer.getSpans(0, buffer.length(), DragState.class);
  
              for (int i = 0; i < ds.length; i++) {
                  buffer.removeSpan(ds[i]);
              }
  
              buffer.setSpan(new DragState(event.getX(), event.getY(),
                              widget.getScrollX(), widget.getScrollY()),
                      0, 0, Spannable.SPAN_MARK_MARK);
              return true;
  
          case MotionEvent.ACTION_UP:
              ds = buffer.getSpans(0, buffer.length(), DragState.class);
  
              for (int i = 0; i < ds.length; i++) {
                  buffer.removeSpan(ds[i]);
              }
  
              if (ds.length > 0 && ds[0].mUsed) {
                  return true;
              } else {
                  return false;
              }
  
          case MotionEvent.ACTION_MOVE:
              ds = buffer.getSpans(0, buffer.length(), DragState.class);
  
              if (ds.length > 0) {
                  if (ds[0].mFarEnough == false) {
                      int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
  
                      if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                          Math.abs(event.getY() - ds[0].mY) >= slop) {
                          ds[0].mFarEnough = true;
                      }
                  }
  
                  if (ds[0].mFarEnough) {
                      ds[0].mUsed = true;
                      boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0
                              || MetaKeyKeyListener.getMetaState(buffer,
                                      MetaKeyKeyListener.META_SHIFT_ON) == 1
                              || MetaKeyKeyListener.getMetaState(buffer,
                                      MetaKeyKeyListener.META_SELECTING) != 0;
  
                      float dx;
                      float dy;
                      if (cap) {
                          // if we're selecting, we want the scroll to go in
                          // the direction of the drag
                          dx = event.getX() - ds[0].mX;
                          dy = event.getY() - ds[0].mY;
                      } else {
                          dx = ds[0].mX - event.getX();
                          dy = ds[0].mY - event.getY();
                      }
                      ds[0].mX = event.getX();
                      ds[0].mY = event.getY();
  
                      int nx = widget.getScrollX() + (int) dx;
                      int ny = widget.getScrollY() + (int) dy;
  
                      int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
                      Layout layout = widget.getLayout();
  
                      ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
                      ny = Math.max(ny, 0);
  
                      int oldX = widget.getScrollX();
                      int oldY = widget.getScrollY();
  
                      scrollTo(widget, layout, nx, ny);
  
                      // If we actually scrolled, then cancel the up action.
                      if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                          widget.cancelLongPress();
                      }
  
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      /**
       * @param widget The text view.
       * @param buffer The text buffer.
       */
      public static int getInitialScrollX(TextView widget, Spannable buffer) {
          DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
          return ds.length > 0 ? ds[0].mScrollX : -1;
      }
  
      /**
       * @param widget The text view.
       * @param buffer The text buffer.
       */
      public static int getInitialScrollY(TextView widget, Spannable buffer) {
          DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
          return ds.length > 0 ? ds[0].mScrollY : -1;
      }
  
      private static class DragState implements NoCopySpan {
          public float mX;
          public float mY;
          public int mScrollX;
          public int mScrollY;
          public boolean mFarEnough;
          public boolean mUsed;
  
          public DragState(float x, float y, int scrollX, int scrollY) {
              mX = x;
              mY = y;
              mScrollX = scrollX;
              mScrollY = scrollY;
          }
      }
  }
  