From 08d81a0347dd86d00e5270d69e2f4f45d1540d3d Mon Sep 17 00:00:00 2001 From: Hrishikesh Kadam Date: Tue, 16 Oct 2018 19:46:36 +0530 Subject: [PATCH 01/31] Last read position CFI generation in js --- ViewPager/.gitignore | 1 - ViewPager/build.gradle | 24 - ViewPager/src/main/AndroidManifest.xml | 14 - .../ryanharter/viewpager/PagerAdapter.java | 27 - .../com/ryanharter/viewpager/ViewPager.java | 3294 ---- ViewPager/src/main/res/values-v11/styles.xml | 11 - ViewPager/src/main/res/values-v14/styles.xml | 12 - ViewPager/src/main/res/values/attrs.xml | 11 - ViewPager/src/main/res/values/strings.xml | 3 - ViewPager/src/main/res/values/styles.xml | 20 - build.gradle | 29 +- folioreader/build.gradle | 34 +- folioreader/src/main/assets/js/Bridge.js | 840 +- .../src/main/assets/js/readium-cfi.umd.js | 14107 ++++++++++++++++ .../java/com/folioreader/model/DisplayUnit.kt | 7 + .../com/folioreader/ui/base/HtmlUtil.java | 4 +- .../ui/folio/activity/FolioActivity.java | 84 +- .../folio/activity/FolioActivityCallback.java | 7 +- .../ui/folio/fragment/FolioPageFragment.java | 41 +- .../java/com/folioreader/util/UiUtil.java | 18 + .../java/com/folioreader/view/FolioWebView.kt | 27 +- sample/build.gradle | 22 +- 22 files changed, 14664 insertions(+), 3973 deletions(-) delete mode 100644 ViewPager/.gitignore delete mode 100644 ViewPager/build.gradle delete mode 100644 ViewPager/src/main/AndroidManifest.xml delete mode 100644 ViewPager/src/main/java/com/ryanharter/viewpager/PagerAdapter.java delete mode 100644 ViewPager/src/main/java/com/ryanharter/viewpager/ViewPager.java delete mode 100644 ViewPager/src/main/res/values-v11/styles.xml delete mode 100644 ViewPager/src/main/res/values-v14/styles.xml delete mode 100644 ViewPager/src/main/res/values/attrs.xml delete mode 100644 ViewPager/src/main/res/values/strings.xml delete mode 100644 ViewPager/src/main/res/values/styles.xml create mode 100644 folioreader/src/main/assets/js/readium-cfi.umd.js create mode 100644 folioreader/src/main/java/com/folioreader/model/DisplayUnit.kt diff --git a/ViewPager/.gitignore b/ViewPager/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/ViewPager/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/ViewPager/build.gradle b/ViewPager/build.gradle deleted file mode 100644 index 0869ca67a..000000000 --- a/ViewPager/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -apply plugin: 'android-library' - -repositories { - mavenCentral() -} - -android { - compileSdkVersion 27 - buildToolsVersion "27.0.0" - - defaultConfig { - minSdkVersion 14 - targetSdkVersion 27 - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 - } -} - -dependencies { - compile 'com.android.support:appcompat-v7:27.0.0' -} diff --git a/ViewPager/src/main/AndroidManifest.xml b/ViewPager/src/main/AndroidManifest.xml deleted file mode 100644 index 7830b68b5..000000000 --- a/ViewPager/src/main/AndroidManifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/ViewPager/src/main/java/com/ryanharter/viewpager/PagerAdapter.java b/ViewPager/src/main/java/com/ryanharter/viewpager/PagerAdapter.java deleted file mode 100644 index f3204a9cd..000000000 --- a/ViewPager/src/main/java/com/ryanharter/viewpager/PagerAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ryanharter.viewpager; - -/** - * {@inheritDoc} - */ -public abstract class PagerAdapter extends android.support.v4.view.PagerAdapter { - - /** - * {@inheritDoc} - * @deprecated Use {@link #getPageSize(int)} instead. - */ - @Override - public float getPageWidth(int position) { - return super.getPageWidth(position); - } - - /** - * Returns the proportional size (width or height depending on orientation) - * of a given page as a percentage of the ViewPager's measured size from (0.f-1.f). - * - * @param position The position of the page requested - * @return Proportional size for the given page position - */ - public float getPageSize(int position) { - return getPageWidth(position); - } -} diff --git a/ViewPager/src/main/java/com/ryanharter/viewpager/ViewPager.java b/ViewPager/src/main/java/com/ryanharter/viewpager/ViewPager.java deleted file mode 100644 index f0545d786..000000000 --- a/ViewPager/src/main/java/com/ryanharter/viewpager/ViewPager.java +++ /dev/null @@ -1,3294 +0,0 @@ -/* - * Copyright (C) 2011 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 com.ryanharter.viewpager; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.support.v4.os.ParcelableCompat; -import android.support.v4.os.ParcelableCompatCreatorCallbacks; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.KeyEventCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewConfigurationCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.support.v4.widget.EdgeEffectCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.view.FocusFinder; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.Interpolator; -import android.widget.Scroller; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * Layout manager that allows the user to flip left and right - * through pages of data. You supply an implementation of a - * {@link PagerAdapter} to generate the pages that the view shows. - * - *

Note this class is currently under early design and - * development. The API will likely change in later updates of - * the compatibility library, requiring changes to the source code - * of apps when they are compiled against the newer version.

- * - *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, - * which is a convenient way to supply and manage the lifecycle of each page. - * There are standard adapters implemented for using fragments with the ViewPager, - * which cover the most common use cases. These are - * {@link android.support.v4.app.FragmentPagerAdapter} and - * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these - * classes have simple code showing how to build a full user interface - * with them. - * - *

Here is a more complicated example of ViewPager, using it in conjuction - * with {@link android.app.ActionBar} tabs. You can find other examples of using - * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. - * - * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java - * complete} - */ -public class ViewPager extends ViewGroup { - private static final String TAG = "ViewPager"; - private static final boolean DEBUG = false; - - private static final boolean USE_CACHE = false; - - private static final int DEFAULT_OFFSCREEN_PAGES = 1; - private static final int MAX_SETTLE_DURATION = 600; // ms - private static final int MIN_DISTANCE_FOR_FLING = 25; // dips - - private static final int DEFAULT_GUTTER_SIZE = 16; // dips - - private static final int MIN_FLING_VELOCITY = 400; // dips - - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.layout_gravity - }; - - private static final int ORIENTATION_HORIZONTAL = 0; - private static final int ORIENTATION_VERTICAL = 1; - - /** - * Used to track what the expected number of items in the adapter should be. - * If the app changes this when we don't expect it, we'll throw a big obnoxious exception. - */ - private int mExpectedAdapterCount; - - static class ItemInfo { - Object object; - int position; - boolean scrolling; - float sizeFactor; - float offset; - } - - private static final Comparator COMPARATOR = new Comparator(){ - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - return lhs.position - rhs.position; - } - }; - - private static final Interpolator sInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - private final ArrayList mItems = new ArrayList(); - private final ItemInfo mTempItem = new ItemInfo(); - - private final Rect mTempRect = new Rect(); - - private PagerAdapter mAdapter; - private int mCurItem; // Index of currently displayed page. - private int mRestoredCurItem = -1; - private Parcelable mRestoredAdapterState = null; - private ClassLoader mRestoredClassLoader = null; - private Scroller mScroller; - private PagerObserver mObserver; - - private int mOrientation = ORIENTATION_HORIZONTAL; - private int mPageMargin; - private Drawable mMarginDrawable; - private int mLeftPageBounds; - private int mTopPageBounds; - private int mRightPageBounds; - private int mBottomPageBounds; - - // Offsets of the first and last items, if known. - // Set during population, used to determine if we are at the beginning - // or end of the pager data set during touch scrolling. - private float mFirstOffset = -Float.MAX_VALUE; - private float mLastOffset = Float.MAX_VALUE; - - private int mChildWidthMeasureSpec; - private int mChildHeightMeasureSpec; - private boolean mInLayout; - - private boolean mScrollingCacheEnabled; - - private boolean mPopulatePending; - private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; - - private boolean mIsBeingDragged; - private boolean mIsUnableToDrag; - private boolean mIgnoreGutter; - private int mDefaultGutterSize; - private int mGutterSize; - private int mTouchSlop; - /** - * Position of the last motion event. - */ - private float mLastMotionX; - private float mLastMotionY; - private float mInitialMotionX; - private float mInitialMotionY; - /** - * ID of the active pointer. This is used to retain consistency during - * drags/flings if multiple pointers are used. - */ - private int mActivePointerId = INVALID_POINTER; - /** - * Sentinel value for no current active pointer. - * Used by {@link #mActivePointerId}. - */ - private static final int INVALID_POINTER = -1; - - /** - * Determines speed during touch scrolling - */ - private VelocityTracker mVelocityTracker; - private int mMinimumVelocity; - private int mMaximumVelocity; - private int mFlingDistance; - private int mCloseEnough; - - // If the pager is at least this close to its final position, complete the scroll - // on touch down and let the user interact with the content inside instead of - // "catching" the flinging pager. - private static final int CLOSE_ENOUGH = 2; // dp - - private boolean mFakeDragging; - private long mFakeDragBeginTime; - - // TODO Do I need separate edge effects, or can I just reuse these with better names like mStartEdge, mEndEdge? - private EdgeEffectCompat mLeftEdge; - private EdgeEffectCompat mRightEdge; - - private boolean mFirstLayout = true; - private boolean mNeedCalculatePageOffsets = false; - private boolean mCalledSuper; - private int mDecorChildCount; - - private OnPageChangeListener mOnPageChangeListener; - private OnPageChangeListener mInternalPageChangeListener; - private OnAdapterChangeListener mAdapterChangeListener; - private PageTransformer mPageTransformer; - private Method mSetChildrenDrawingOrderEnabled; - - private static final int DRAW_ORDER_DEFAULT = 0; - private static final int DRAW_ORDER_FORWARD = 1; - private static final int DRAW_ORDER_REVERSE = 2; - private int mDrawingOrder; - private ArrayList mDrawingOrderedChildren; - private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator(); - - /** - * Indicates that the pager is in an idle, settled state. The current page - * is fully in view and no animation is in progress. - */ - public static final int SCROLL_STATE_IDLE = 0; - - /** - * Indicates that the pager is currently being dragged by the user. - */ - public static final int SCROLL_STATE_DRAGGING = 1; - - /** - * Indicates that the pager is in the process of settling to a final position. - */ - public static final int SCROLL_STATE_SETTLING = 2; - - private final Runnable mEndScrollRunnable = new Runnable() { - public void run() { - setScrollState(SCROLL_STATE_IDLE); - populate(); - } - }; - - private int mScrollState = SCROLL_STATE_IDLE; - - /** - * Callback interface for responding to changing state of the selected page. - */ - public interface OnPageChangeListener { - - /** - * This method will be invoked when the current page is scrolled, either as part - * of a programmatically initiated smooth scroll or a user initiated touch scroll. - * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param positionOffset Value from [0, 1) indicating the offset from the page at position. - * @param positionOffsetPixels Value in pixels indicating the offset from position. - */ - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); - - /** - * This method will be invoked when a new page becomes selected. Animation is not - * necessarily complete. - * - * @param position Position index of the new selected page. - */ - public void onPageSelected(int position); - - /** - * Called when the scroll state changes. Useful for discovering when the user - * begins dragging, when the pager is automatically settling to the current page, - * or when it is fully stopped/idle. - * - * @param state The new scroll state. - * @see ViewPager#SCROLL_STATE_IDLE - * @see ViewPager#SCROLL_STATE_DRAGGING - * @see ViewPager#SCROLL_STATE_SETTLING - */ - public void onPageScrollStateChanged(int state); - } - - /** - * Simple implementation of the {@link OnPageChangeListener} interface with stub - * implementations of each method. Extend this if you do not intend to override - * every method of {@link OnPageChangeListener}. - */ - public static class SimpleOnPageChangeListener implements OnPageChangeListener { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - // This space for rent - } - - @Override - public void onPageSelected(int position) { - // This space for rent - } - - @Override - public void onPageScrollStateChanged(int state) { - // This space for rent - } - } - - /** - * A PageTransformer is invoked whenever a visible/attached page is scrolled. - * This offers an opportunity for the application to apply a custom transformation - * to the page views using animation properties. - * - *

As property animation is only supported as of Android 3.0 and forward, - * setting a PageTransformer on a ViewPager on earlier platform versions will - * be ignored.

- */ - public interface PageTransformer { - /** - * Apply a property transformation to the given page. - * - * @param page Apply the transformation to this page - * @param position Position of page relative to the current front-and-center - * position of the pager. 0 is front and center. 1 is one full - * page position to the right, and -1 is one page position to the left. - */ - public void transformPage(View page, float position); - } - - /** - * Used internally to monitor when adapters are switched. - */ - interface OnAdapterChangeListener { - public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); - } - - /** - * Used internally to tag special types of child views that should be added as - * pager decorations by default. - */ - interface Decor {} - - public ViewPager(Context context) { - this(context, null); - } - - public ViewPager(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ViewPager(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.ViewPager, defStyle, 0); - mOrientation = a.getInt(R.styleable.ViewPager_orientation, ORIENTATION_HORIZONTAL); - a.recycle(); - - initViewPager(); - } - - void initViewPager() { - setWillNotDraw(false); - setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); - setFocusable(true); - final Context context = getContext(); - mScroller = new Scroller(context, sInterpolator); - - final ViewConfiguration configuration = ViewConfiguration.get(context); - final float density = context.getResources().getDisplayMetrics().density; - - mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); - mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mLeftEdge = new EdgeEffectCompat(context); - mRightEdge = new EdgeEffectCompat(context); - - mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); - mCloseEnough = (int) (CLOSE_ENOUGH * density); - mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); - - ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); - - if (ViewCompat.getImportantForAccessibility(this) - == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - ViewCompat.setImportantForAccessibility(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - @Override - protected void onDetachedFromWindow() { - removeCallbacks(mEndScrollRunnable); - super.onDetachedFromWindow(); - } - - private void setScrollState(int newState) { - if (mScrollState == newState) { - return; - } - - mScrollState = newState; - if (mPageTransformer != null) { - // PageTransformers can do complex things that benefit from hardware layers. - enableLayers(newState != SCROLL_STATE_IDLE); - } - if (mOnPageChangeListener != null) { - mOnPageChangeListener.onPageScrollStateChanged(newState); - } - } - - /** - * Set a PagerAdapter that will supply views for this pager as needed. - * - * @param adapter Adapter to use - */ - public void setAdapter(PagerAdapter adapter) { - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(mObserver); - mAdapter.startUpdate(this); - for (int i = 0; i < mItems.size(); i++) { - final ItemInfo ii = mItems.get(i); - mAdapter.destroyItem(this, ii.position, ii.object); - } - mAdapter.finishUpdate(this); - mItems.clear(); - removeNonDecorViews(); - mCurItem = 0; - scrollTo(0, 0); - } - - final PagerAdapter oldAdapter = mAdapter; - mAdapter = adapter; - mExpectedAdapterCount = 0; - - if (mAdapter != null) { - if (mObserver == null) { - mObserver = new PagerObserver(); - } - mAdapter.registerDataSetObserver(mObserver); - mPopulatePending = false; - final boolean wasFirstLayout = mFirstLayout; - mFirstLayout = true; - mExpectedAdapterCount = mAdapter.getCount(); - if (mRestoredCurItem >= 0) { - mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); - setCurrentItemInternal(mRestoredCurItem, false, true); - mRestoredCurItem = -1; - mRestoredAdapterState = null; - mRestoredClassLoader = null; - } else if (!wasFirstLayout) { - populate(); - } else { - requestLayout(); - } - } - - if (mAdapterChangeListener != null && oldAdapter != adapter) { - mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); - } - } - - private void removeNonDecorViews() { - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) { - removeViewAt(i); - i--; - } - } - } - - /** - * Retrieve the current adapter supplying pages. - * - * @return The currently registered PagerAdapter - */ - public PagerAdapter getAdapter() { - return mAdapter; - } - - void setOnAdapterChangeListener(OnAdapterChangeListener listener) { - mAdapterChangeListener = listener; - } - - public int getOrientation() { - return mOrientation; - } - - public boolean isOrientationHorizontal() { - return mOrientation == ORIENTATION_HORIZONTAL; - } - - private int getClientWidth() { - return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - } - - private int getClientHeight() { - return getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); - } - - /** - * Set the currently selected page. If the ViewPager has already been through its first - * layout with its current adapter there will be a smooth animated transition between - * the current item and the specified item. - * - * @param item Item index to select - */ - public void setCurrentItem(int item) { - mPopulatePending = false; - setCurrentItemInternal(item, !mFirstLayout, false); - } - - /** - * Set the currently selected page. - * - * @param item Item index to select - * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately - */ - public void setCurrentItem(int item, boolean smoothScroll) { - mPopulatePending = false; - setCurrentItemInternal(item, smoothScroll, false); - } - - public int getCurrentItem() { - return mCurItem; - } - - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { - setCurrentItemInternal(item, smoothScroll, always, 0); - } - - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { - if (mAdapter == null || mAdapter.getCount() <= 0) { - setScrollingCacheEnabled(false); - return; - } - if (!always && mCurItem == item && mItems.size() != 0) { - setScrollingCacheEnabled(false); - return; - } - - if (item < 0) { - item = 0; - } else if (item >= mAdapter.getCount()) { - item = mAdapter.getCount() - 1; - } - final int pageLimit = mOffscreenPageLimit; - if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { - // We are doing a jump by more than one page. To avoid - // glitches, we want to keep all current pages in the view - // until the scroll ends. - for (int i=0; iNote: Prior to Android 3.0 the property animation APIs did not exist. - * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

- * - * @param reverseDrawingOrder true if the supplied PageTransformer requires page views - * to be drawn from last to first instead of first to last. - * @param transformer PageTransformer that will modify each page's animation properties - */ - public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { - if (Build.VERSION.SDK_INT >= 11) { - final boolean hasTransformer = transformer != null; - final boolean needsPopulate = hasTransformer != (mPageTransformer != null); - mPageTransformer = transformer; - setChildrenDrawingOrderEnabledCompat(hasTransformer); - if (hasTransformer) { - mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD; - } else { - mDrawingOrder = DRAW_ORDER_DEFAULT; - } - if (needsPopulate) populate(); - } - } - - void setChildrenDrawingOrderEnabledCompat(boolean enable) { - if (Build.VERSION.SDK_INT >= 7) { - if (mSetChildrenDrawingOrderEnabled == null) { - try { - mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( - "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); - } - } - try { - mSetChildrenDrawingOrderEnabled.invoke(this, enable); - } catch (Exception e) { - Log.e(TAG, "Error changing children drawing order", e); - } - } - } - - @Override - protected int getChildDrawingOrder(int childCount, int i) { - final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i; - final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex; - return result; - } - - /** - * Set a separate OnPageChangeListener for internal use by the support library. - * - * @param listener Listener to set - * @return The old listener that was set, if any. - */ - OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { - OnPageChangeListener oldListener = mInternalPageChangeListener; - mInternalPageChangeListener = listener; - return oldListener; - } - - /** - * Returns the number of pages that will be retained to either side of the - * current page in the view hierarchy in an idle state. Defaults to 1. - * - * @return How many pages will be kept offscreen on either side - * @see #setOffscreenPageLimit(int) - */ - public int getOffscreenPageLimit() { - return mOffscreenPageLimit; - } - - /** - * Set the number of pages that should be retained to either side of the - * current page in the view hierarchy in an idle state. Pages beyond this - * limit will be recreated from the adapter when needed. - * - *

This is offered as an optimization. If you know in advance the number - * of pages you will need to support or have lazy-loading mechanisms in place - * on your pages, tweaking this setting can have benefits in perceived smoothness - * of paging animations and interaction. If you have a small number of pages (3-4) - * that you can keep active all at once, less time will be spent in layout for - * newly created view subtrees as the user pages back and forth.

- * - *

You should keep this limit low, especially if your pages have complex layouts. - * This setting defaults to 1.

- * - * @param limit How many pages will be kept offscreen in an idle state. - */ - public void setOffscreenPageLimit(int limit) { - if (limit < DEFAULT_OFFSCREEN_PAGES) { - Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + - DEFAULT_OFFSCREEN_PAGES); - limit = DEFAULT_OFFSCREEN_PAGES; - } - if (limit != mOffscreenPageLimit) { - mOffscreenPageLimit = limit; - populate(); - } - } - - /** - * Set the margin between pages. - * - * @param marginPixels Distance between adjacent pages in pixels - * @see #getPageMargin() - * @see #setPageMarginDrawable(Drawable) - * @see #setPageMarginDrawable(int) - */ - public void setPageMargin(int marginPixels) { - final int oldMargin = mPageMargin; - mPageMargin = marginPixels; - - int spacing = 0; - if (isOrientationHorizontal()) { - spacing = getWidth(); - } else { - spacing = getHeight(); - } - recomputeScrollPosition(spacing, spacing, spacing, spacing, marginPixels, oldMargin); - - requestLayout(); - } - - /** - * Return the margin between pages. - * - * @return The size of the margin in pixels - */ - public int getPageMargin() { - return mPageMargin; - } - - /** - * Set a drawable that will be used to fill the margin between pages. - * - * @param d Drawable to display between pages - */ - public void setPageMarginDrawable(Drawable d) { - mMarginDrawable = d; - if (d != null) refreshDrawableState(); - setWillNotDraw(d == null); - invalidate(); - } - - /** - * Set a drawable that will be used to fill the margin between pages. - * - * @param resId Resource ID of a drawable to display between pages - */ - public void setPageMarginDrawable(int resId) { - setPageMarginDrawable(getContext().getResources().getDrawable(resId)); - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == mMarginDrawable; - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - final Drawable d = mMarginDrawable; - if (d != null && d.isStateful()) { - d.setState(getDrawableState()); - } - } - - // We want the duration of the page snap animation to be influenced by the distance that - // the screen has to travel, however, we don't want this duration to be effected in a - // purely linear fashion. Instead, we use this method to moderate the effect that the distance - // of travel has on the overall snap duration. - float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - /** - * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. - * - * @param x the number of pixels to scroll by on the X axis - * @param y the number of pixels to scroll by on the Y axis - */ - void smoothScrollTo(int x, int y) { - smoothScrollTo(x, y, 0); - } - - /** - * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. - * - * @param x the number of pixels to scroll by on the X axis - * @param y the number of pixels to scroll by on the Y axis - * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) - */ - void smoothScrollTo(int x, int y, int velocity) { - if (getChildCount() == 0) { - // Nothing to do. - setScrollingCacheEnabled(false); - return; - } - int sx = getScrollX(); - int sy = getScrollY(); - int dx = x - sx; - int dy = y - sy; - if (dx == 0 && dy == 0) { - completeScroll(false); - populate(); - setScrollState(SCROLL_STATE_IDLE); - return; - } - - setScrollingCacheEnabled(true); - setScrollState(SCROLL_STATE_SETTLING); - - final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); - final int halfSize = size / 2; - final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / size); - final float distance = halfSize + halfSize * - distanceInfluenceForSnapDuration(distanceRatio); - - int duration = 0; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - final float pageSize = size * mAdapter.getPageSize(mCurItem); - final float pageDelta = (float) Math.abs(dx) / (pageSize + mPageMargin); - duration = (int) ((pageDelta + 1) * 100); - } - duration = Math.min(duration, MAX_SETTLE_DURATION); - - mScroller.startScroll(sx, sy, dx, dy, duration); - ViewCompat.postInvalidateOnAnimation(this); - } - - ItemInfo addNewItem(int position, int index) { - ItemInfo ii = new ItemInfo(); - ii.position = position; - ii.object = mAdapter.instantiateItem(this, position); - ii.sizeFactor = mAdapter.getPageSize(position); - if (index < 0 || index >= mItems.size()) { - mItems.add(ii); - } else { - mItems.add(index, ii); - } - return ii; - } - - void dataSetChanged() { - // This method only gets called if our observer is attached, so mAdapter is non-null. - - final int adapterCount = mAdapter.getCount(); - mExpectedAdapterCount = adapterCount; - boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && - mItems.size() < adapterCount; - int newCurrItem = mCurItem; - - boolean isUpdating = false; - for (int i = 0; i < mItems.size(); i++) { - final ItemInfo ii = mItems.get(i); - final int newPos = mAdapter.getItemPosition(ii.object); - - if (newPos == PagerAdapter.POSITION_UNCHANGED) { - continue; - } - - if (newPos == PagerAdapter.POSITION_NONE) { - mItems.remove(i); - i--; - - if (!isUpdating) { - mAdapter.startUpdate(this); - isUpdating = true; - } - - mAdapter.destroyItem(this, ii.position, ii.object); - needPopulate = true; - - if (mCurItem == ii.position) { - // Keep the current item in the valid range - newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); - needPopulate = true; - } - continue; - } - - if (ii.position != newPos) { - if (ii.position == mCurItem) { - // Our current item changed position. Follow it. - newCurrItem = newPos; - } - - ii.position = newPos; - needPopulate = true; - } - } - - if (isUpdating) { - mAdapter.finishUpdate(this); - } - - Collections.sort(mItems, COMPARATOR); - - if (needPopulate) { - // Reset our known page widths; populate will recompute them. - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) { - lp.sizeFactor = 0.f; - } - } - - setCurrentItemInternal(newCurrItem, false, true); - requestLayout(); - } - } - - void populate() { - populate(mCurItem); - } - - void populate(int newCurrentItem) { - ItemInfo oldCurInfo = null; - int focusDirection = View.FOCUS_FORWARD; - if (mCurItem != newCurrentItem) { - if (isOrientationHorizontal()) { - focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; - } else { - focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP; - } - oldCurInfo = infoForPosition(mCurItem); - mCurItem = newCurrentItem; - } - - if (mAdapter == null) { - sortChildDrawingOrder(); - return; - } - - // Bail now if we are waiting to populate. This is to hold off - // on creating views from the time the user releases their finger to - // fling to a new position until we have finished the scroll to - // that position, avoiding glitches from happening at that point. - if (mPopulatePending) { - if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); - sortChildDrawingOrder(); - return; - } - - // Also, don't populate until we are attached to a window. This is to - // avoid trying to populate before we have restored our view hierarchy - // state and conflicting with what is restored. - if (getWindowToken() == null) { - return; - } - - mAdapter.startUpdate(this); - - final int pageLimit = mOffscreenPageLimit; - final int startPos = Math.max(0, mCurItem - pageLimit); - final int N = mAdapter.getCount(); - final int endPos = Math.min(N-1, mCurItem + pageLimit); - - if (N != mExpectedAdapterCount) { - String resName; - try { - resName = getResources().getResourceName(getId()); - } catch (Resources.NotFoundException e) { - resName = Integer.toHexString(getId()); - } - throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + - " contents without calling PagerAdapter#notifyDataSetChanged!" + - " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + - " Pager id: " + resName + - " Pager class: " + getClass() + - " Problematic adapter: " + mAdapter.getClass()); - } - - // Locate the currently focused item or add it if needed. - int curIndex = -1; - ItemInfo curItem = null; - for (curIndex = 0; curIndex < mItems.size(); curIndex++) { - final ItemInfo ii = mItems.get(curIndex); - if (ii.position >= mCurItem) { - if (ii.position == mCurItem) curItem = ii; - break; - } - } - - if (curItem == null && N > 0) { - curItem = addNewItem(mCurItem, curIndex); - } - - // Fill 3x the available width or up to the number of offscreen - // pages requested to either side, whichever is larger. - // If we have no current item we have no work to do. - if (curItem != null) { - float extraSizeStart = 0.f; - int itemIndex = curIndex - 1; - ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - final int paddingStart = isOrientationHorizontal() ? getPaddingLeft() : getPaddingTop(); - final int clientSize = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); - final float startSizeNeeded = clientSize <= 0 ? 0 : - 2.f - curItem.sizeFactor + (float) paddingStart / (float) clientSize; - for (int pos = mCurItem - 1; pos >= 0; pos--) { - if (extraSizeStart >= startSizeNeeded && pos < startPos) { - if (ii == null) { - break; - } - if (pos == ii.position && !ii.scrolling) { - mItems.remove(itemIndex); - mAdapter.destroyItem(this, pos, ii.object); - if (DEBUG) { - Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); - } - itemIndex--; - curIndex--; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } - } else if (ii != null && pos == ii.position) { - extraSizeStart += ii.sizeFactor; - itemIndex--; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } else { - ii = addNewItem(pos, itemIndex + 1); - extraSizeStart += ii.sizeFactor; - curIndex++; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } - } - - float extraSizeEnd = curItem.sizeFactor; - itemIndex = curIndex + 1; - if (extraSizeEnd < 2.f) { - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - final int paddingEnd = isOrientationHorizontal() ? getPaddingRight() : getPaddingBottom(); - final float endSizeNeeded = clientSize <= 0 ? 0 : - (float) paddingEnd / (float) clientSize + 2.f; - for (int pos = mCurItem + 1; pos < N; pos++) { - if (extraSizeEnd >= endSizeNeeded && pos > endPos) { - if (ii == null) { - break; - } - if (pos == ii.position && !ii.scrolling) { - mItems.remove(itemIndex); - mAdapter.destroyItem(this, pos, ii.object); - if (DEBUG) { - Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); - } - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } - } else if (ii != null && pos == ii.position) { - extraSizeEnd += ii.sizeFactor; - itemIndex++; - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } else { - ii = addNewItem(pos, itemIndex); - itemIndex++; - extraSizeEnd += ii.sizeFactor; - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } - } - } - - calculatePageOffsets(curItem, curIndex, oldCurInfo); - } - - if (DEBUG) { - Log.i(TAG, "Current page list:"); - for (int i=0; i(); - } else { - mDrawingOrderedChildren.clear(); - } - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - mDrawingOrderedChildren.add(child); - } - Collections.sort(mDrawingOrderedChildren, sPositionComparator); - } - } - - private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { - final int N = mAdapter.getCount(); - final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); - final float marginOffset = size > 0 ? (float) mPageMargin / size : 0; - // Fix up offsets for later layout. - if (oldCurInfo != null) { - final int oldCurPosition = oldCurInfo.position; - // Base offsets off of oldCurInfo. - if (oldCurPosition < curItem.position) { - int itemIndex = 0; - ItemInfo ii = null; - float offset = oldCurInfo.offset + oldCurInfo.sizeFactor + marginOffset; - for (int pos = oldCurPosition + 1; - pos <= curItem.position && itemIndex < mItems.size(); pos++) { - ii = mItems.get(itemIndex); - while (pos > ii.position && itemIndex < mItems.size() - 1) { - itemIndex++; - ii = mItems.get(itemIndex); - } - while (pos < ii.position) { - // We don't have an item populated for this, - // ask the adapter for an offset. - offset += mAdapter.getPageSize(pos) + marginOffset; - pos++; - } - ii.offset = offset; - offset += ii.sizeFactor + marginOffset; - } - } else if (oldCurPosition > curItem.position) { - int itemIndex = mItems.size() - 1; - ItemInfo ii = null; - float offset = oldCurInfo.offset; - for (int pos = oldCurPosition - 1; - pos >= curItem.position && itemIndex >= 0; pos--) { - ii = mItems.get(itemIndex); - while (pos < ii.position && itemIndex > 0) { - itemIndex--; - ii = mItems.get(itemIndex); - } - while (pos > ii.position) { - // We don't have an item populated for this, - // ask the adapter for an offset. - offset -= mAdapter.getPageSize(pos) + marginOffset; - pos--; - } - offset -= ii.sizeFactor + marginOffset; - ii.offset = offset; - } - } - } - - // Base all offsets off of curItem. - final int itemCount = mItems.size(); - float offset = curItem.offset; - int pos = curItem.position - 1; - mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; - mLastOffset = curItem.position == N - 1 ? - curItem.offset + curItem.sizeFactor - 1 : Float.MAX_VALUE; - // Previous pages - for (int i = curIndex - 1; i >= 0; i--, pos--) { - final ItemInfo ii = mItems.get(i); - while (pos > ii.position) { - offset -= mAdapter.getPageSize(pos--) + marginOffset; - } - offset -= ii.sizeFactor + marginOffset; - ii.offset = offset; - if (ii.position == 0) mFirstOffset = offset; - } - offset = curItem.offset + curItem.sizeFactor + marginOffset; - pos = curItem.position + 1; - // Next pages - for (int i = curIndex + 1; i < itemCount; i++, pos++) { - final ItemInfo ii = mItems.get(i); - while (pos < ii.position) { - offset += mAdapter.getPageSize(pos++) + marginOffset; - } - if (ii.position == N - 1) { - mLastOffset = offset + ii.sizeFactor - 1; - } - ii.offset = offset; - offset += ii.sizeFactor + marginOffset; - } - - mNeedCalculatePageOffsets = false; - } - - /** - * This is the persistent state that is saved by ViewPager. Only needed - * if you are creating a sublass of ViewPager that must save its own - * state, in which case it should implement a subclass of this which - * contains that state. - */ - public static class SavedState extends BaseSavedState { - int position; - Parcelable adapterState; - ClassLoader loader; - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeInt(position); - out.writeParcelable(adapterState, flags); - } - - @Override - public String toString() { - return "FragmentPager.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " position=" + position + "}"; - } - - public static final Parcelable.Creator CREATOR - = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }); - - SavedState(Parcel in, ClassLoader loader) { - super(in); - if (loader == null) { - loader = getClass().getClassLoader(); - } - position = in.readInt(); - adapterState = in.readParcelable(loader); - this.loader = loader; - } - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.position = mCurItem; - if (mAdapter != null) { - ss.adapterState = mAdapter.saveState(); - } - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - SavedState ss = (SavedState)state; - super.onRestoreInstanceState(ss.getSuperState()); - - if (mAdapter != null) { - mAdapter.restoreState(ss.adapterState, ss.loader); - setCurrentItemInternal(ss.position, false, true); - } else { - mRestoredCurItem = ss.position; - mRestoredAdapterState = ss.adapterState; - mRestoredClassLoader = ss.loader; - } - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - if (!checkLayoutParams(params)) { - params = generateLayoutParams(params); - } - final LayoutParams lp = (LayoutParams) params; - lp.isDecor |= child instanceof Decor; - if (mInLayout) { - if (lp != null && lp.isDecor) { - throw new IllegalStateException("Cannot add pager decor view during layout"); - } - lp.needsMeasure = true; - addViewInLayout(child, index, params); - } else { - super.addView(child, index, params); - } - - if (USE_CACHE) { - if (child.getVisibility() != GONE) { - child.setDrawingCacheEnabled(mScrollingCacheEnabled); - } else { - child.setDrawingCacheEnabled(false); - } - } - } - - @Override - public void removeView(View view) { - if (mInLayout) { - removeViewInLayout(view); - } else { - super.removeView(view); - } - } - - ItemInfo infoForChild(View child) { - for (int i=0; i 0 && !mItems.isEmpty()) { - final int paddingStart = isOrientationHorizontal() ? getPaddingLeft() : getPaddingTop(); - final int paddingEnd = isOrientationHorizontal() ? getPaddingRight() - : getPaddingBottom(); - final int sizeWithMargin = size - paddingStart - paddingEnd + margin; - final int oldSizeWithMargin = oldSize - paddingStart - paddingEnd + oldMargin; - final int xPos = getScrollX(); - final int yPos = getScrollY(); - - final float pageOffset = (float) (isOrientationHorizontal() ? xPos : yPos) / oldSizeWithMargin; - final int newXOffsetPixels = isOrientationHorizontal() ? (int) (pageOffset * sizeWithMargin) : xPos; - final int newYOffsetPixels = isOrientationHorizontal() ? yPos : (int) (pageOffset * sizeWithMargin); - - scrollTo(newXOffsetPixels, newYOffsetPixels); - if (!mScroller.isFinished()) { - // We now return to your regularly schedules scroll, already in progress. - final int newDuration = mScroller.getDuration() - mScroller.timePassed(); - ItemInfo targetInfo = infoForPosition(mCurItem); - if (isOrientationHorizontal()) { - mScroller.startScroll(newXOffsetPixels, 0, - (int) (targetInfo.offset * size), 0, newDuration); - } else { - mScroller.startScroll(0, newYOffsetPixels, - (int) (targetInfo.offset * size), 0, newDuration); - } - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int count = getChildCount(); - int width = r - l; - int height = b - t; - int paddingLeft = getPaddingLeft(); - int paddingTop = getPaddingTop(); - int paddingRight = getPaddingRight(); - int paddingBottom = getPaddingBottom(); - final int scrollX = getScrollX(); - final int scrollY = getScrollY(); - - int decorCount = 0; - - // First pass - decor views. We need to do this in two passes so that - // we have the proper offsets for non-decor views later. - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - int childLeft = 0; - int childTop = 0; - if (lp.isDecor) { - final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; - final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; - switch (hgrav) { - default: - childLeft = paddingLeft; - break; - case Gravity.LEFT: - childLeft = paddingLeft; - paddingLeft += child.getMeasuredWidth(); - break; - case Gravity.CENTER_HORIZONTAL: - childLeft = Math.max((width - child.getMeasuredWidth()) / 2, - paddingLeft); - break; - case Gravity.RIGHT: - childLeft = width - paddingRight - child.getMeasuredWidth(); - paddingRight += child.getMeasuredWidth(); - break; - } - switch (vgrav) { - default: - childTop = paddingTop; - break; - case Gravity.TOP: - childTop = paddingTop; - paddingTop += child.getMeasuredHeight(); - break; - case Gravity.CENTER_VERTICAL: - childTop = Math.max((height - child.getMeasuredHeight()) / 2, - paddingTop); - break; - case Gravity.BOTTOM: - childTop = height - paddingBottom - child.getMeasuredHeight(); - paddingBottom += child.getMeasuredHeight(); - break; - } - if (isOrientationHorizontal()) { - childLeft += scrollX; - } else { - childTop += scrollY; - } - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), - childTop + child.getMeasuredHeight()); - decorCount++; - } - } - } - - int childSize = 0; - if (isOrientationHorizontal()) { - childSize = width - paddingLeft - paddingRight; - } else { - childSize = height - paddingTop - paddingBottom; - } - // Page views. Do this once we have the right padding offsets from above. - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - ItemInfo ii; - if (!lp.isDecor && (ii = infoForChild(child)) != null) { - int loff = (int) (childSize * ii.offset); - int childLeft = paddingLeft + (isOrientationHorizontal() ? loff : 0); - int childTop = paddingTop + (isOrientationHorizontal() ? 0 : loff); - if (lp.needsMeasure) { - // This was added during layout and needs measurement. - // Do it now that we know what we're working with. - lp.needsMeasure = false; - int widthSpec = 0, heightSpec = 0; - if (isOrientationHorizontal()) { - widthSpec = MeasureSpec.makeMeasureSpec( - (int) (childSize * lp.sizeFactor), - MeasureSpec.EXACTLY); - heightSpec = MeasureSpec.makeMeasureSpec( - (int) (height - paddingTop - paddingBottom), - MeasureSpec.EXACTLY); - } else { - widthSpec = MeasureSpec.makeMeasureSpec( - (int) (width - paddingLeft - paddingRight), - MeasureSpec.EXACTLY); - heightSpec = MeasureSpec.makeMeasureSpec( - (int) (childSize * lp.sizeFactor), - MeasureSpec.EXACTLY); - } - child.measure(widthSpec, heightSpec); - } - if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object - + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() - + "x" + child.getMeasuredHeight()); - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), - childTop + child.getMeasuredHeight()); - } - } - } - mLeftPageBounds = paddingLeft; - mTopPageBounds = paddingTop; - mRightPageBounds = width - paddingRight; - mBottomPageBounds = height - paddingBottom; - mDecorChildCount = decorCount; - - if (mFirstLayout) { - scrollToItem(mCurItem, false, 0, false); - } - mFirstLayout = false; - } - - @Override - public void computeScroll() { - if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - - if (oldX != x || oldY != y) { - scrollTo(x, y); - if (!pageScrolled(isOrientationHorizontal() ? x : y)) { - mScroller.abortAnimation(); - if (isOrientationHorizontal()) { - scrollTo(0, y); - } else { - scrollTo(x, 0); - } - } - } - - // Keep on drawing until the animation has finished. - ViewCompat.postInvalidateOnAnimation(this); - return; - } - - // Done with scroll, clean up state. - completeScroll(true); - } - - private boolean pageScrolled(int pos) { - if (mItems.size() == 0) { - mCalledSuper = false; - onPageScrolled(0, 0, 0); - if (!mCalledSuper) { - throw new IllegalStateException( - "onPageScrolled did not call superclass implementation"); - } - return false; - } - final ItemInfo ii = infoForCurrentScrollPosition(); - final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); - final int sizeWithMargin = size + mPageMargin; - final float marginOffset = (float) mPageMargin / size; - final int currentPage = ii.position; - final float pageOffset = (((float) pos / size) - ii.offset) / - (ii.sizeFactor + marginOffset); - final int offsetPixels = (int) (pageOffset * sizeWithMargin); - - mCalledSuper = false; - onPageScrolled(currentPage, pageOffset, offsetPixels); - if (!mCalledSuper) { - throw new IllegalStateException( - "onPageScrolled did not call superclass implementation"); - } - return true; - } - - /** - * This method will be invoked when the current page is scrolled, either as part - * of a programmatically initiated smooth scroll or a user initiated touch scroll. - * If you override this method you must call through to the superclass implementation - * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled - * returns. - * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param offset Value from [0, 1) indicating the offset from the page at position. - * @param offsetPixels Value in pixels indicating the offset from position. - */ - protected void onPageScrolled(int position, float offset, int offsetPixels) { - // Offset any decor views if needed - keep them on-screen at all times. - if (mDecorChildCount > 0) { - // TODO This is where I start getting tired. Refactor this better later. - if (isOrientationHorizontal()) { - final int scrollX = getScrollX(); - int paddingLeft = getPaddingLeft(); - int paddingRight = getPaddingRight(); - final int width = getWidth(); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) continue; - - final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; - int childLeft = 0; - switch (hgrav) { - default: - childLeft = paddingLeft; - break; - case Gravity.LEFT: - childLeft = paddingLeft; - paddingLeft += child.getWidth(); - break; - case Gravity.CENTER_HORIZONTAL: - childLeft = Math.max((width - child.getMeasuredWidth()) / 2, - paddingLeft); - break; - case Gravity.RIGHT: - childLeft = width - paddingRight - child.getMeasuredWidth(); - paddingRight += child.getMeasuredWidth(); - break; - } - childLeft += scrollX; - - final int childOffset = childLeft - child.getLeft(); - if (childOffset != 0) { - child.offsetLeftAndRight(childOffset); - } - } - } else { - final int scrollY = getScrollY(); - int paddingTop = getPaddingTop(); - int paddingBottom = getPaddingBottom(); - final int height = getHeight(); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) continue; - - final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; - int childTop = 0; - switch (vgrav) { - default: - childTop = paddingTop; - break; - case Gravity.TOP: - childTop = paddingTop; - paddingTop += child.getHeight(); - break; - case Gravity.CENTER_VERTICAL: - childTop = Math.max((height - child.getMeasuredHeight()) / 2, - paddingTop); - break; - case Gravity.BOTTOM: - childTop = height - paddingBottom - child.getMeasuredHeight(); - paddingBottom += child.getMeasuredHeight(); - break; - } - childTop += scrollY; - - final int childOffset = childTop - child.getTop(); - if (childOffset != 0) { - child.offsetTopAndBottom(childOffset); - } - } - } - } - - if (mOnPageChangeListener != null) { - mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); - } - if (mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); - } - - if (mPageTransformer != null) { - final boolean horizontal = isOrientationHorizontal(); - final int scroll = horizontal ? getScrollX() : getScrollY(); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (lp.isDecor) continue; - - float transformPos; - if (horizontal) { - transformPos = (float) (child.getLeft() - scroll) / getClientWidth(); - } else { - transformPos = (float) (child.getTop() - scroll) / getClientHeight(); - } - mPageTransformer.transformPage(child, transformPos); - } - } - - mCalledSuper = true; - } - - private void completeScroll(boolean postEvents) { - boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; - if (needPopulate) { - // Done with scroll, no longer want to cache view drawing. - setScrollingCacheEnabled(false); - mScroller.abortAnimation(); - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - if (oldX != x || oldY != y) { - scrollTo(x, y); - } - } - mPopulatePending = false; - for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0); - } else { - return (x < mGutterSize && dx > 0) || (x > getHeight() - mGutterSize && dx < 0); - } - } - - private void enableLayers(boolean enable) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final int layerType = enable ? - ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; - ViewCompat.setLayerType(getChildAt(i), layerType, null); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - /* - * This method JUST determines whether we want to intercept the motion. - * If we return true, onMotionEvent will be called and we do the actual - * scrolling there. - */ - - final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; - - // Always take care of the touch gesture being complete. - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - // Release the drag. - if (DEBUG) Log.v(TAG, "Intercept done!"); - mIsBeingDragged = false; - mIsUnableToDrag = false; - mActivePointerId = INVALID_POINTER; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - return false; - } - - // Nothing more to do here if we have decided whether or not we - // are dragging. - if (action != MotionEvent.ACTION_DOWN) { - if (mIsBeingDragged) { - if (DEBUG) Log.v(TAG, "Intercept returning true!"); - return true; - } - if (mIsUnableToDrag) { - if (DEBUG) Log.v(TAG, "Intercept returning false!"); - return false; - } - } - - switch (action) { - case MotionEvent.ACTION_MOVE: { - /* - * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check - * whether the user has moved far enough from his original down touch. - */ - - /* - * Locally do absolute value. mLastMotionY is set to the y value - * of the down event. - */ - final int activePointerId = mActivePointerId; - if (activePointerId == INVALID_POINTER) { - // If we don't have a valid id, the touch down wasn't on content. - break; - } - - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); - final float x = MotionEventCompat.getX(ev, pointerIndex); - final float dx = x - mLastMotionX; - final float xDiff = Math.abs(dx); - final float y = MotionEventCompat.getY(ev, pointerIndex); - final float dy = y - mLastMotionY; - final float yDiff = Math.abs(y - mInitialMotionY); - if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); - - if (isOrientationHorizontal()) { - if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && - canScroll(this, false, (int) dx, (int) x, (int) y)) { - // Nested view has scrollable area under this point. Let it be handled there. - mLastMotionX = x; - mLastMotionY = y; - mIsUnableToDrag = true; - return false; - } - if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - setScrollState(SCROLL_STATE_DRAGGING); - mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : - mInitialMotionX - mTouchSlop; - mLastMotionY = y; - setScrollingCacheEnabled(true); - } else if (yDiff > mTouchSlop) { - // The finger has moved enough in the vertical - // direction to be counted as a drag... abort - // any attempt to drag horizontally, to work correctly - // with children that have scrolling containers. - if (DEBUG) Log.v(TAG, "Starting unable to drag!"); - mIsUnableToDrag = true; - } - if (mIsBeingDragged) { - // Scroll to follow the motion event - if (performDrag(x)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - } else { - if (dy != 0 && !isGutterDrag(mLastMotionY, dy) && - canScroll(this, false, (int) dx, (int) x, (int) y)) { - // Nested view has scrollable area under this point. Let it be handled there. - mLastMotionX = x; - mLastMotionY = y; - mIsUnableToDrag = true; - return false; - } - if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - setScrollState(SCROLL_STATE_DRAGGING); - mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop : - mInitialMotionY - mTouchSlop; - mLastMotionX = x; - setScrollingCacheEnabled(true); - } else if (xDiff > mTouchSlop) { - // The finger has moved enough in the vertical - // direction to be counted as a drag... abort - // any attempt to drag horizontally, to work correctly - // with children that have scrolling containers. - if (DEBUG) Log.v(TAG, "Starting unable to drag!"); - mIsUnableToDrag = true; - } - if (mIsBeingDragged) { - // Scroll to follow the motion event - if (performDrag(y)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - } - break; - } - - case MotionEvent.ACTION_DOWN: { - /* - * Remember location of down touch. - * ACTION_DOWN always refers to pointer index 0. - */ - mLastMotionX = mInitialMotionX = ev.getX(); - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mIsUnableToDrag = false; - - mScroller.computeScrollOffset(); - int distance = 0; - if (isOrientationHorizontal()) { - distance = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); - } else { - distance = Math.abs(mScroller.getFinalY() - mScroller.getCurrY()); - } - if (mScrollState == SCROLL_STATE_SETTLING && distance > mCloseEnough) { - // Let the user 'catch' the pager as it animates. - mScroller.abortAnimation(); - mPopulatePending = false; - populate(); - mIsBeingDragged = true; - setScrollState(SCROLL_STATE_DRAGGING); - } else { - completeScroll(false); - mIsBeingDragged = false; - } - - if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY - + " mIsBeingDragged=" + mIsBeingDragged - + "mIsUnableToDrag=" + mIsUnableToDrag); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - /* - * The only time we want to intercept motion events is if we are in the - * drag mode. - */ - return mIsBeingDragged; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mFakeDragging) { - // A fake drag is in progress already, ignore this real one - // but still eat the touch events. - // (It is likely that the user is multi-touching the screen.) - return true; - } - - if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { - // Don't handle edge touches immediately -- they may actually belong to one of our - // descendants. - return false; - } - - if (mAdapter == null || mAdapter.getCount() == 0) { - // Nothing to present or scroll; nothing to touch. - return false; - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - final int action = ev.getAction(); - boolean needsInvalidate = false; - - switch (action & MotionEventCompat.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - mScroller.abortAnimation(); - mPopulatePending = false; - populate(); - mIsBeingDragged = true; - setScrollState(SCROLL_STATE_DRAGGING); - - // Remember where the motion event started - mLastMotionX = mInitialMotionX = ev.getX(); - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - break; - } - case MotionEvent.ACTION_MOVE: - if (!mIsBeingDragged) { - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, pointerIndex); - final float xDiff = Math.abs(x - mLastMotionX); - final float y = MotionEventCompat.getY(ev, pointerIndex); - final float yDiff = Math.abs(y - mLastMotionY); - if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); - if (isOrientationHorizontal()) { - if (xDiff > mTouchSlop && xDiff > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : - mInitialMotionX - mTouchSlop; - mLastMotionY = y; - setScrollState(SCROLL_STATE_DRAGGING); - setScrollingCacheEnabled(true); - } - } else { - if (yDiff > mTouchSlop && yDiff > xDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop : - mInitialMotionY - mTouchSlop; - mLastMotionX = x; - setScrollState(SCROLL_STATE_DRAGGING); - setScrollingCacheEnabled(true); - } - } - } - // Not else! Note that mIsBeingDragged can be set above. - if (mIsBeingDragged) { - // Scroll to follow the motion event - final int activePointerIndex = MotionEventCompat.findPointerIndex( - ev, mActivePointerId); - float x = 0; - if (isOrientationHorizontal()) { - x = MotionEventCompat.getX(ev, activePointerIndex); - } else { - x = MotionEventCompat.getY(ev, activePointerIndex); - } - needsInvalidate |= performDrag(x); - } - break; - case MotionEvent.ACTION_UP: - if (mIsBeingDragged) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - mPopulatePending = true; - final ItemInfo ii = infoForCurrentScrollPosition(); - final int currentPage = ii.position; - - int initialVelocity, totalDelta; - float pageOffset; - if (isOrientationHorizontal()) { - initialVelocity = (int) VelocityTrackerCompat.getXVelocity( - velocityTracker, mActivePointerId); - final int width = getClientWidth(); - final int scrollX = getScrollX(); - pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor; - final int activePointerIndex = - MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, activePointerIndex); - totalDelta = (int) (x - mInitialMotionX); - } else { - initialVelocity = (int) VelocityTrackerCompat.getYVelocity( - velocityTracker, mActivePointerId); - final int height = getClientHeight(); - final int scrollY = getScrollY(); - pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor; - final int activePointerIndex = - MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float y = MotionEventCompat.getY(ev, activePointerIndex); - totalDelta = (int) (y - mInitialMotionY); - } - - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); - setCurrentItemInternal(nextPage, true, true, initialVelocity); - - mActivePointerId = INVALID_POINTER; - endDrag(); - needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); - } - break; - case MotionEvent.ACTION_CANCEL: - if (mIsBeingDragged) { - scrollToItem(mCurItem, true, 0, false); - mActivePointerId = INVALID_POINTER; - endDrag(); - needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); - } - break; - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - final float x = MotionEventCompat.getX(ev, index); - final float y = MotionEventCompat.getY(ev, index); - mLastMotionX = x; - mLastMotionY = y; - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - mLastMotionX = MotionEventCompat.getX(ev, - MotionEventCompat.findPointerIndex(ev, mActivePointerId)); - mLastMotionY = MotionEventCompat.getY(ev, - MotionEventCompat.findPointerIndex(ev, mActivePointerId)); - break; - } - if (needsInvalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - return true; - } - - private boolean performDrag(float pos) { - boolean needsInvalidate = false; - - if (isOrientationHorizontal()) { - final float deltaX = mLastMotionX - pos; - mLastMotionX = pos; - - float oldScrollX = getScrollX(); - float scrollX = oldScrollX + deltaX; - final int width = getClientWidth(); - - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - boolean leftAbsolute = true; - boolean rightAbsolute = true; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftAbsolute = false; - leftBound = firstItem.offset * width; - } - if (lastItem.position != mAdapter.getCount() - 1) { - rightAbsolute = false; - rightBound = lastItem.offset * width; - } - - if (scrollX < leftBound) { - if (leftAbsolute) { - float over = leftBound - scrollX; - needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); - } - scrollX = leftBound; - } else if (scrollX > rightBound) { - if (rightAbsolute) { - float over = scrollX - rightBound; - needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); - } - scrollX = rightBound; - } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); - } else { - final float deltaY = mLastMotionY - pos; - mLastMotionY = pos; - - float oldScrollY = getScrollY(); - float scrollY = oldScrollY + deltaY; - final int height = getClientHeight(); - - float topBound = height * mFirstOffset; - float bottomBound = height * mLastOffset; - boolean topAbsolute = true; - boolean bottomAbsolute = true; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - topAbsolute = false; - topBound = firstItem.offset * height; - } - if (lastItem.position != mAdapter.getCount() - 1) { - bottomAbsolute = false; - bottomBound = lastItem.offset * height; - } - - if (scrollY < topBound) { - if (topAbsolute) { - float over = topBound - scrollY; - needsInvalidate = mLeftEdge.onPull(Math.abs(over) / height); - } - scrollY = topBound; - } else if (scrollY > bottomBound) { - if (bottomAbsolute) { - float over = scrollY - bottomBound; - needsInvalidate = mRightEdge.onPull(Math.abs(over) / height); - } - scrollY = bottomBound; - } - // Don't lose the rounded component - mLastMotionX += scrollY - (int) scrollY; - scrollTo(getScrollX(), (int) scrollY); - pageScrolled((int) scrollY); - } - - return needsInvalidate; - } - - /** - * @return Info about the page at the current scroll position. - * This can be synthetic for a missing middle page; the 'object' field can be null. - */ - private ItemInfo infoForCurrentScrollPosition() { - final int scroll = isOrientationHorizontal() ? getScrollX() : getScrollY(); - final int size = isOrientationHorizontal() ? getClientWidth() : getClientHeight(); - final float scrollOffset = size > 0 ? (float) scroll / size : 0; - final float marginOffset = size > 0 ? (float) mPageMargin / size : 0; - int lastPos = -1; - float lastOffset = 0.f; - float lastSize = 0.f; - boolean first = true; - - ItemInfo lastItem = null; - for (int i = 0; i < mItems.size(); i++) { - ItemInfo ii = mItems.get(i); - float offset; - if (!first && ii.position != lastPos + 1) { - // Create a synthetic item for a missing page. - ii = mTempItem; - ii.offset = lastOffset + lastSize + marginOffset; - ii.position = lastPos + 1; - ii.sizeFactor = mAdapter.getPageSize(ii.position); - i--; - } - offset = ii.offset; - - final float startBound = offset; - final float endBound = offset + ii.sizeFactor + marginOffset; - if (first || scrollOffset >= startBound) { - if (scrollOffset < endBound || i == mItems.size() - 1) { - return ii; - } - } else { - return lastItem; - } - first = false; - lastPos = ii.position; - lastOffset = offset; - lastSize = ii.sizeFactor; - lastItem = ii; - } - - return lastItem; - } - - private int determineTargetPage(int currentPage, float pageOffset, int velocity, int delta) { - int targetPage; - if (Math.abs(delta) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { - targetPage = velocity > 0 ? currentPage : currentPage + 1; - } else { - final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; - targetPage = (int) (currentPage + pageOffset + truncator); - } - - if (mItems.size() > 0) { - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - - // Only let the user target pages we have items for - targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); - } - - return targetPage; - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - boolean needsInvalidate = false; - - final int overScrollMode = ViewCompat.getOverScrollMode(this); - if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && - mAdapter != null && mAdapter.getCount() > 1)) { - if (!mLeftEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int width = isOrientationHorizontal() - ? getHeight() - getPaddingTop() - getPaddingBottom() - : getWidth() - getPaddingLeft() - getPaddingRight(); - final int height = isOrientationHorizontal() - ? getWidth() - getPaddingLeft() - getPaddingRight() - : getHeight() - getPaddingTop() - getPaddingBottom(); - - if (isOrientationHorizontal()) { - canvas.rotate(270); - canvas.translate(-width + getPaddingTop(), mFirstOffset * height); - } - mLeftEdge.setSize(width, height); - needsInvalidate |= mLeftEdge.draw(canvas); - canvas.restoreToCount(restoreCount); - } - if (!mRightEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int width = isOrientationHorizontal() - ? getHeight() - getPaddingTop() - getPaddingBottom() - : getWidth() - getPaddingLeft() - getPaddingRight(); - final int height = isOrientationHorizontal() - ? getWidth() - getPaddingLeft() - getPaddingRight() - : getHeight() - getPaddingTop() - getPaddingBottom(); - - if (isOrientationHorizontal()) { - canvas.rotate(90); - canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * height); - } else { - canvas.rotate(180); - canvas.translate(-width, -(mLastOffset + 1) * height); - } - mRightEdge.setSize(width, height); - needsInvalidate |= mRightEdge.draw(canvas); - canvas.restoreToCount(restoreCount); - } - } else { - mLeftEdge.finish(); - mRightEdge.finish(); - } - - if (needsInvalidate) { - // Keep animating - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Draw the margin drawable between pages if needed. - if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { - if (isOrientationHorizontal()) { - final int scrollX = getScrollX(); - final int width = getWidth(); - - final float marginOffset = (float) mPageMargin / width; - int itemIndex = 0; - ItemInfo ii = mItems.get(0); - float offset = ii.offset; - final int itemCount = mItems.size(); - final int firstPos = ii.position; - final int lastPos = mItems.get(itemCount - 1).position; - for (int pos = firstPos; pos < lastPos; pos++) { - while (pos > ii.position && itemIndex < itemCount) { - ii = mItems.get(++itemIndex); - } - - float drawAt; - if (pos == ii.position) { - drawAt = (ii.offset + ii.sizeFactor) * width; - offset = ii.offset + ii.sizeFactor + marginOffset; - } else { - float widthFactor = mAdapter.getPageWidth(pos); - drawAt = (offset + widthFactor) * width; - offset += widthFactor + marginOffset; - } - - if (drawAt + mPageMargin > scrollX) { - mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, - (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); - mMarginDrawable.draw(canvas); - } - - if (drawAt > scrollX + width) { - break; // No more visible, no sense in continuing - } - } - } else { - final int scrollY = getScrollY(); - final int height = getHeight(); - - final float marginOffset = (float) mPageMargin / height; - int itemIndex = 0; - ItemInfo ii = mItems.get(0); - float offset = ii.offset; - final int itemCount = mItems.size(); - final int firstPos = ii.position; - final int lastPos = mItems.get(itemCount - 1).position; - for (int pos = firstPos; pos < lastPos; pos++) { - while (pos > ii.position && itemIndex < itemCount) { - ii = mItems.get(++itemIndex); - } - - float drawAt; - if (pos == ii.position) { - drawAt = (ii.offset + ii.sizeFactor) * height; - offset = ii.offset + ii.sizeFactor + marginOffset; - } else { - float sizeFactor = mAdapter.getPageSize(pos); - drawAt = (offset + sizeFactor) * height; - offset += sizeFactor + marginOffset; - } - - if (drawAt + mPageMargin > scrollY) { - mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt, - mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f)); - mMarginDrawable.draw(canvas); - } - - if (drawAt > scrollY + height) { - break; // No more visible, no sense in continuing - } - } - } - } - } - - /** - * Start a fake drag of the pager. - * - *

A fake drag can be useful if you want to synchronize the motion of the ViewPager - * with the touch scrolling of another view, while still letting the ViewPager - * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) - * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call - * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. - * - *

During a fake drag the ViewPager will ignore all touch events. If a real drag - * is already in progress, this method will return false. - * - * @return true if the fake drag began successfully, false if it could not be started. - * - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean beginFakeDrag() { - if (mIsBeingDragged) { - return false; - } - mFakeDragging = true; - setScrollState(SCROLL_STATE_DRAGGING); - if (isOrientationHorizontal()) { - mInitialMotionX = mLastMotionX = 0; - } else { - mInitialMotionY = mLastMotionY = 0; - } - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - mVelocityTracker.clear(); - } - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - mFakeDragBeginTime = time; - return true; - } - - /** - * End a fake drag of the pager. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - */ - public void endFakeDrag() { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - - final ItemInfo ii = infoForCurrentScrollPosition(); - final int currentPage = ii.position; - - int initialVelocity, totalDelta; - float pageOffset; - if (isOrientationHorizontal()) { - initialVelocity = (int) VelocityTrackerCompat.getXVelocity( - velocityTracker, mActivePointerId); - mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); - pageOffset = (((float) scrollX / width) - ii.offset) / ii.sizeFactor; - totalDelta = (int) (mLastMotionX - mInitialMotionX); - } else { - initialVelocity = (int) VelocityTrackerCompat.getYVelocity( - velocityTracker, mActivePointerId); - mPopulatePending = true; - final int height = getClientHeight(); - final int scrollY = getScrollY(); - pageOffset = (((float) scrollY / height) - ii.offset) / ii.sizeFactor; - totalDelta = (int) (mLastMotionY - mInitialMotionY); - } - - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); - setCurrentItemInternal(nextPage, true, true, initialVelocity); - endDrag(); - - mFakeDragging = false; - } - - /** - * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. - * - * @param offset Offset in pixels to drag by. - * @see #beginFakeDrag() - * @see #endFakeDrag() - */ - public void fakeDragBy(float offset) { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - if (isOrientationHorizontal()) { - mLastMotionX += offset; - - float oldScrollX = getScrollX(); - float scrollX = oldScrollX - offset; - final int width = getClientWidth(); - - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftBound = firstItem.offset * width; - } - if (lastItem.position != mAdapter.getCount() - 1) { - rightBound = lastItem.offset * width; - } - - if (scrollX < leftBound) { - scrollX = leftBound; - } else if (scrollX > rightBound) { - scrollX = rightBound; - } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); - - // Synthesize an event for the VelocityTracker. - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, - mLastMotionX, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - } else { - mLastMotionY += offset; - - float oldScrollY = getScrollY(); - float scrollY = oldScrollY - offset; - final int height = getClientHeight(); - - float topBound = height * mFirstOffset; - float bottomBound = height * mLastOffset; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - topBound = firstItem.offset * height; - } - if (lastItem.position != mAdapter.getCount() - 1) { - bottomBound = lastItem.offset * height; - } - - if (scrollY < topBound) { - scrollY = topBound; - } else if (scrollY > bottomBound) { - scrollY = bottomBound; - } - // Don't lose the rounded component - mLastMotionY += scrollY - (int) scrollY; - scrollTo(getScrollX(), (int) scrollY); - pageScrolled((int) scrollY); - - // Synthesize an event for the VelocityTracker. - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, - 0, mLastMotionY, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - } - } - - /** - * Returns true if a fake drag is in progress. - * - * @return true if currently in a fake drag, false otherwise. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean isFakeDragging() { - return mFakeDragging; - } - - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); - mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex); - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - - private void endDrag() { - mIsBeingDragged = false; - mIsUnableToDrag = false; - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private void setScrollingCacheEnabled(boolean enabled) { - if (mScrollingCacheEnabled != enabled) { - mScrollingCacheEnabled = enabled; - if (USE_CACHE) { - final int size = getChildCount(); - for (int i = 0; i < size; ++i) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - child.setDrawingCacheEnabled(enabled); - } - } - } - } - } - - public boolean canScrollHorizontally(int direction) { - if (mAdapter == null) { - return false; - } - - final int width = getClientWidth(); - final int scrollX = getScrollX(); - if (direction < 0) { - return (scrollX > (int) (width * mFirstOffset)); - } else if (direction > 0) { - return (scrollX < (int) (width * mLastOffset)); - } else { - return false; - } - } - - public boolean canScrollVertically(int direction) { - if (mAdapter == null) { - return false; - } - - final int height = getClientHeight(); - final int scrollY = getScrollY(); - if (direction < 0) { - return (scrollY > (int) (height * mFirstOffset)); - } else if (direction > 0) { - return (scrollY < (int) (height * mLastOffset)); - } else { - return false; - } - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v View to test for horizontal scrollability - * @param checkV Whether the view v passed should itself be checked for scrollability (true), - * or just its children (false). - * @param dx Delta scrolled in pixels - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, x + scrollX - child.getLeft(), - y + scrollY - child.getTop())) { - return true; - } - } - } - - boolean canScroll = false; - if (isOrientationHorizontal()) { - canScroll = ViewCompat.canScrollHorizontally(v, -dx); - } else { - canScroll = ViewCompat.canScrollVertically(v, -dx); - } - return checkV && canScroll; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Let the focused view and/or our descendants get the key first - return super.dispatchKeyEvent(event) || executeKeyEvent(event); - } - - /** - * You can call this function yourself to have the scroll view perform - * scrolling from a key event, just as if the event had been dispatched to - * it by the view hierarchy. - * - * @param event The key event to execute. - * @return Return true if the event was handled, else false. - */ - public boolean executeKeyEvent(KeyEvent event) { - boolean handled = false; - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (isOrientationHorizontal()) - handled = arrowScroll(FOCUS_LEFT); - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (isOrientationHorizontal()) - handled = arrowScroll(FOCUS_RIGHT); - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (!isOrientationHorizontal()) - handled = arrowScroll(FOCUS_UP); - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (!isOrientationHorizontal()) - handled = arrowScroll(FOCUS_DOWN); - break; - case KeyEvent.KEYCODE_TAB: - if (Build.VERSION.SDK_INT >= 11) { - // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD - // before Android 3.0. Ignore the tab key on those devices. - if (KeyEventCompat.hasNoModifiers(event)) { - handled = arrowScroll(FOCUS_FORWARD); - } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { - handled = arrowScroll(FOCUS_BACKWARD); - } - } - break; - } - } - return handled; - } - - public boolean arrowScroll(int direction) { - View currentFocused = findFocus(); - if (currentFocused == this) { - currentFocused = null; - } else if (currentFocused != null) { - boolean isChild = false; - for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { - if (parent == this) { - isChild = true; - break; - } - } - if (!isChild) { - // This would cause the focus search down below to fail in fun ways. - final StringBuilder sb = new StringBuilder(); - sb.append(currentFocused.getClass().getSimpleName()); - for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { - sb.append(" => ").append(parent.getClass().getSimpleName()); - } - Log.e(TAG, "arrowScroll tried to find focus based on non-child " + - "current focused view " + sb.toString()); - currentFocused = null; - } - } - - boolean handled = false; - - View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, - direction); - if (nextFocused != null && nextFocused != currentFocused) { - if (direction == View.FOCUS_LEFT) { - // If there is nothing to the left, or this is causing us to - // jump to the right, then what we really want to do is page left. - final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; - final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; - if (currentFocused != null && nextLeft >= currLeft) { - handled = pageLeft(); - } else { - handled = nextFocused.requestFocus(); - } - } else if (direction == View.FOCUS_RIGHT) { - // If there is nothing to the right, or this is causing us to - // jump to the left, then what we really want to do is page right. - final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; - final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; - if (currentFocused != null && nextLeft <= currLeft) { - handled = pageRight(); - } else { - handled = nextFocused.requestFocus(); - } - } else if (direction == View.FOCUS_UP) { - final int nextUp = getChildRectInPagerCoordinates(mTempRect, nextFocused).top; - final int currUp = getChildRectInPagerCoordinates(mTempRect, currentFocused).top; - if (currentFocused != null && nextUp >= currUp) { - handled = pageLeft(); - } else { - handled = nextFocused.requestFocus(); - } - } else if (direction == View.FOCUS_DOWN) { - final int nextUp = getChildRectInPagerCoordinates(mTempRect, nextFocused).top; - final int currUp = getChildRectInPagerCoordinates(mTempRect, currentFocused).top; - if (currentFocused != null && nextUp <= currUp) { - handled = pageRight(); - } else { - handled = nextFocused.requestFocus(); - } - } - } else if (direction == FOCUS_LEFT || direction == FOCUS_UP || direction == FOCUS_BACKWARD) { - // Trying to move left and nothing there; try to page. - handled = pageLeft(); - } else if (direction == FOCUS_RIGHT || direction == FOCUS_DOWN || direction == FOCUS_FORWARD) { - // Trying to move right and nothing there; try to page. - handled = pageRight(); - } - if (handled) { - playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); - } - return handled; - } - - private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { - if (outRect == null) { - outRect = new Rect(); - } - if (child == null) { - outRect.set(0, 0, 0, 0); - return outRect; - } - outRect.left = child.getLeft(); - outRect.right = child.getRight(); - outRect.top = child.getTop(); - outRect.bottom = child.getBottom(); - - ViewParent parent = child.getParent(); - while (parent instanceof ViewGroup && parent != this) { - final ViewGroup group = (ViewGroup) parent; - outRect.left += group.getLeft(); - outRect.right += group.getRight(); - outRect.top += group.getTop(); - outRect.bottom += group.getBottom(); - - parent = group.getParent(); - } - return outRect; - } - - boolean pageLeft() { - if (mCurItem > 0) { - setCurrentItem(mCurItem-1, true); - return true; - } - return false; - } - - boolean pageRight() { - if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { - setCurrentItem(mCurItem+1, true); - return true; - } - return false; - } - - /** - * We only want the current page that is being shown to be focusable. - */ - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - final int focusableCount = views.size(); - - final int descendantFocusability = getDescendantFocusability(); - - if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - child.addFocusables(views, direction, focusableMode); - } - } - } - } - - // we add ourselves (if focusable) in all cases except for when we are - // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is - // to avoid the focus search finding layouts when a more precise search - // among the focusable children would be more interesting. - if ( - descendantFocusability != FOCUS_AFTER_DESCENDANTS || - // No focusable descendants - (focusableCount == views.size())) { - // Note that we can't call the superclass here, because it will - // add all views in. So we need to do the same thing View does. - if (!isFocusable()) { - return; - } - if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && - isInTouchMode() && !isFocusableInTouchMode()) { - return; - } - if (views != null) { - views.add(this); - } - } - } - - /** - * We only want the current page that is being shown to be touchable. - */ - @Override - public void addTouchables(ArrayList views) { - // Note that we don't call super.addTouchables(), which means that - // we don't call View.addTouchables(). This is okay because a ViewPager - // is itself not touchable. - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - child.addTouchables(views); - } - } - } - } - - /** - * We only want the current page that is being shown to be focusable. - */ - @Override - protected boolean onRequestFocusInDescendants(int direction, - Rect previouslyFocusedRect) { - int index; - int increment; - int end; - int count = getChildCount(); - if ((direction & FOCUS_FORWARD) != 0) { - index = 0; - increment = 1; - end = count; - } else { - index = count - 1; - increment = -1; - end = -1; - } - for (int i = index; i != end; i += increment) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - if (child.requestFocus(direction, previouslyFocusedRect)) { - return true; - } - } - } - } - return false; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // Dispatch scroll events from this ViewPager. - if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) { - return super.dispatchPopulateAccessibilityEvent(event); - } - - // Dispatch all other accessibility events from the current page. - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - final ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem && - child.dispatchPopulateAccessibilityEvent(event)) { - return true; - } - } - } - - return false; - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return generateDefaultLayoutParams(); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - class MyAccessibilityDelegate extends AccessibilityDelegateCompat { - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - event.setClassName(ViewPager.class.getName()); - final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain(); - recordCompat.setScrollable(canScroll()); - if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED - && mAdapter != null) { - recordCompat.setItemCount(mAdapter.getCount()); - recordCompat.setFromIndex(mCurItem); - recordCompat.setToIndex(mCurItem); - } - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setClassName(ViewPager.class.getName()); - info.setScrollable(canScroll()); - if (canScrollHorizontally(1)) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); - } - if (canScrollHorizontally(-1)) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); - } - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - switch (action) { - case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { - if (canScrollHorizontally(1)) { - setCurrentItem(mCurItem + 1); - return true; - } - } return false; - case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { - if (canScrollHorizontally(-1)) { - setCurrentItem(mCurItem - 1); - return true; - } - } return false; - } - return false; - } - - private boolean canScroll() { - return (mAdapter != null) && (mAdapter.getCount() > 1); - } - } - - private class PagerObserver extends DataSetObserver { - @Override - public void onChanged() { - dataSetChanged(); - } - @Override - public void onInvalidated() { - dataSetChanged(); - } - } - - /** - * Layout parameters that should be supplied for views added to a - * ViewPager. - */ - public static class LayoutParams extends ViewGroup.LayoutParams { - /** - * true if this view is a decoration on the pager itself and not - * a view supplied by the adapter. - */ - public boolean isDecor; - - /** - * Gravity setting for use on decor views only: - * Where to position the view page within the overall ViewPager - * container; constants are defined in {@link android.view.Gravity}. - */ - public int gravity; - - /** - * Width as a 0-1 multiplier of the measured pager width - */ - float sizeFactor = 0.f; - - /** - * true if this view was added during layout and needs to be measured - * before being positioned. - */ - boolean needsMeasure; - - /** - * Adapter position this view is for if !isDecor - */ - int position; - - /** - * Current child index within the ViewPager that this view occupies - */ - int childIndex; - - public LayoutParams() { - super(MATCH_PARENT, MATCH_PARENT); - } - - public LayoutParams(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - gravity = a.getInteger(0, Gravity.TOP); - a.recycle(); - } - } - - static class ViewPositionComparator implements Comparator { - @Override - public int compare(View lhs, View rhs) { - final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); - final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams(); - if (llp.isDecor != rlp.isDecor) { - return llp.isDecor ? 1 : -1; - } - return llp.position - rlp.position; - } - } -} \ No newline at end of file diff --git a/ViewPager/src/main/res/values-v11/styles.xml b/ViewPager/src/main/res/values-v11/styles.xml deleted file mode 100644 index 3c02242ad..000000000 --- a/ViewPager/src/main/res/values-v11/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/ViewPager/src/main/res/values-v14/styles.xml b/ViewPager/src/main/res/values-v14/styles.xml deleted file mode 100644 index a91fd0372..000000000 --- a/ViewPager/src/main/res/values-v14/styles.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/ViewPager/src/main/res/values/attrs.xml b/ViewPager/src/main/res/values/attrs.xml deleted file mode 100644 index be14d4ce3..000000000 --- a/ViewPager/src/main/res/values/attrs.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/ViewPager/src/main/res/values/strings.xml b/ViewPager/src/main/res/values/strings.xml deleted file mode 100644 index cc18fb0a3..000000000 --- a/ViewPager/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ViewPager - diff --git a/ViewPager/src/main/res/values/styles.xml b/ViewPager/src/main/res/values/styles.xml deleted file mode 100644 index 6ce89c7ba..000000000 --- a/ViewPager/src/main/res/values/styles.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/build.gradle b/build.gradle index f9459da6b..30a5491fb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.KOTLIN_VERSION = '1.2.71' + ext.kotlinVersion = "1.2.71" repositories { google() @@ -16,36 +16,35 @@ buildscript { classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } allprojects { repositories { + mavenLocal() google() jcenter() - maven { url "https://dl.bintray.com/mobisystech/maven" } maven { url "https://jitpack.io" } - mavenLocal() } } ext { - FOLIOREADER_SDK_VERSION = '0.5.1' - R2_STREAMER_KOTLIN_VERSION = '1.0.3-5' - R2_SHARED_KOTLIN_VERSION = '1.0.3-2' + folioreaderSdkVersion = "0.5.1" + r2StreamerKotlinVersion = "1.0.3-6.dev" + r2SharedKotlinVersion = "1.0.3-2.local" - VERSION_NAME = "1.0" - VERSION_CODE = 1 + projectVersionCode = 1 + projectVersionName = "1.0" - ANDROID_MIN_SDK = 19 - ANDROID_COMPILE_SDK_VERSION = 28 - ANDROID_TARGET_SDK_VERSION = 28 - ANDROID_LIB_VERSION = '28.0.0' + androidMinSdkVersion = 19 + androidCompileSdkVersion = 28 + androidTargetSdkVersion = 28 + androidSupportLibVersion = "28.0.0" - CONSTRAINT_LAYOUT_VERSION = "1.1.3" + constraintLayoutVersion = "1.1.3" - JACKSON_VERSION = '2.8.6' + jacksonVersion = "2.8.6" } task clean(type: Delete) { diff --git a/folioreader/build.gradle b/folioreader/build.gradle index 942c94b1b..349c22dcd 100644 --- a/folioreader/build.gradle +++ b/folioreader/build.gradle @@ -17,7 +17,7 @@ ext { siteUrl = 'https://github.com/FolioReader/FolioReader-Android' gitUrl = 'https://github.com/FolioReader/FolioReader-Android.git' - libraryVersion = FOLIOREADER_SDK_VERSION + libraryVersion = folioreaderSdkVersion developerId = 'mobisystech' developerName = 'Folio Reader' @@ -30,13 +30,13 @@ ext { android { useLibrary 'org.apache.http.legacy' - compileSdkVersion ANDROID_COMPILE_SDK_VERSION + compileSdkVersion androidCompileSdkVersion defaultConfig { - minSdkVersion ANDROID_MIN_SDK - targetSdkVersion ANDROID_TARGET_SDK_VERSION - versionCode VERSION_CODE - versionName VERSION_NAME + minSdkVersion androidMinSdkVersion + targetSdkVersion androidTargetSdkVersion + versionCode projectVersionCode + versionName projectVersionName vectorDrawables.useSupportLibrary = true } @@ -85,27 +85,27 @@ apply from: '../folioreader/bintray/installv1.gradle' dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation "com.android.support.constraint:constraint-layout:$CONSTRAINT_LAYOUT_VERSION" - implementation "com.android.support:appcompat-v7:$ANDROID_LIB_VERSION" - implementation "com.android.support:recyclerview-v7:$ANDROID_LIB_VERSION" - implementation "com.android.support:support-v4:$ANDROID_LIB_VERSION" - implementation "com.android.support:design:$ANDROID_LIB_VERSION" + implementation "com.android.support.constraint:constraint-layout:$constraintLayoutVersion" + implementation "com.android.support:appcompat-v7:$androidSupportLibVersion" + implementation "com.android.support:recyclerview-v7:$androidSupportLibVersion" + implementation "com.android.support:support-v4:$androidSupportLibVersion" + implementation "com.android.support:design:$androidSupportLibVersion" implementation 'org.slf4j:slf4j-android:1.7.25' implementation 'com.daimajia.swipelayout:library:1.2.0@aar' //Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation 'org.greenrobot:eventbus:3.1.1' - implementation "com.fasterxml.jackson.core:jackson-core:$JACKSON_VERSION" - implementation "com.fasterxml.jackson.core:jackson-annotations:$JACKSON_VERSION" - implementation "com.fasterxml.jackson.core:jackson-databind:$JACKSON_VERSION" + implementation "com.fasterxml.jackson.core:jackson-core:$jacksonVersion" + implementation "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion" + implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" // R2 modules - implementation "com.github.codetoart:r2-shared-kotlin:$R2_SHARED_KOTLIN_VERSION" - implementation("com.github.codetoart:r2-streamer-kotlin:$R2_STREAMER_KOTLIN_VERSION") { + implementation "com.github.codetoart:r2-shared-kotlin:$r2SharedKotlinVersion" + implementation("com.github.codetoart:r2-streamer-kotlin:$r2StreamerKotlinVersion") { exclude group: "org.slf4j", module: "slf4j-api" } diff --git a/folioreader/src/main/assets/js/Bridge.js b/folioreader/src/main/assets/js/Bridge.js index 87b80c48e..c271363a8 100755 --- a/folioreader/src/main/assets/js/Bridge.js +++ b/folioreader/src/main/assets/js/Bridge.js @@ -1,6 +1,6 @@ // // Bridge.js -// FolioReaderKit +// FolioReader-Android // // Created by Heberti Almeida on 06/05/15. // Copyright (c) 2015 Folio Reader. All rights reserved. @@ -10,20 +10,44 @@ var thisHighlight; var audioMarkClass; var wordsPerMinute = 180; +var Direction = Object.freeze({ + VERTICAL: "VERTICAL", + HORIZONTAL: "HORIZONTAL" +}); + +var DisplayUnit = Object.freeze({ + PX: "PX", + DP: "DP", + CSS_PX: "CSS_PX" +}); + +var scrollWidth; +var horizontalInterval; +var horizontalIntervalPeriod = 1000; +var horizontalIntervalCounter = 0; +var horizontalIntervalLimit = 3000; + +var searchResults = []; +var lastSearchQuery = null; +var testCounter = 0; +var searchResultsInvisible = true; + +var viewportRect; + // Class manipulation -function hasClass(ele,cls) { - return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')); +function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); } -function addClass(ele,cls) { - if (!hasClass(ele,cls)) ele.className += " "+cls; +function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += " " + cls; } -function removeClass(ele,cls) { - if (hasClass(ele,cls)) { - var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)'); - ele.className=ele.className.replace(reg,' '); - } +function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + ele.className = ele.className.replace(reg, ' '); + } } // Menu colors @@ -51,12 +75,12 @@ function getBodyText() { // Method that gets the Rect of current selected text // and returns in a JSON format -var getRectForSelectedText = function(elm) { +var getRectForSelectedText = function (elm) { if (typeof elm === "undefined") elm = window.getSelection().getRangeAt(0); var rect = elm.getBoundingClientRect(); return "{{" + rect.left + "," + rect.top + "}, {" + rect.width + "," + rect.height + "}}"; -} +}; // Reading time function getReadingTime() { @@ -78,10 +102,10 @@ function scrollAnchor(id) { */ function removeAllClasses(className) { var els = document.body.getElementsByClassName(className) - if( els.length > 0 ) - for( i = 0; i <= els.length; i++) { - els[i].classList.remove(className); - } + if (els.length > 0) + for (i = 0; i <= els.length; i++) { + els[i].classList.remove(className); + } } /** @@ -98,13 +122,13 @@ function audioMarkID(className, id) { el.classList.add(className) } -function setMediaOverlayStyle(style){ +function setMediaOverlayStyle(style) { document.documentElement.classList.remove("mediaOverlayStyle0", "mediaOverlayStyle1", "mediaOverlayStyle2") document.documentElement.classList.add(style) } function setMediaOverlayStyleColors(color, colorHighlight) { - var stylesheet = document.styleSheets[document.styleSheets.length-1]; + var stylesheet = document.styleSheets[document.styleSheets.length - 1]; // stylesheet.insertRule(".mediaOverlayStyle0 span.epub-media-overlay-playing { background: "+colorHighlight+" !important }") // stylesheet.insertRule(".mediaOverlayStyle1 span.epub-media-overlay-playing { border-color: "+color+" !important }") // stylesheet.insertRule(".mediaOverlayStyle2 span.epub-media-overlay-playing { color: "+color+" !important }") @@ -115,7 +139,7 @@ var currentIndex = -1; function findSentenceWithIDInView(els) { // @NOTE: is `span` too limiting? - for(indx in els) { + for (indx in els) { var element = els[indx]; // Horizontal scroll @@ -128,8 +152,8 @@ function findSentenceWithIDInView(els) { return element; } - // Vertical - } else if(element.offsetTop > document.body.scrollTop) { + // Vertical + } else if (element.offsetTop > document.body.scrollTop) { currentIndex = indx; return element; } @@ -139,8 +163,8 @@ function findSentenceWithIDInView(els) { } function findNextSentenceInArray(els) { - if(currentIndex >= 0) { - currentIndex ++; + if (currentIndex >= 0) { + currentIndex++; return els[currentIndex]; } @@ -152,7 +176,7 @@ function resetCurrentSentenceIndex() { } function rewindCurrentIndex() { - currentIndex = currentIndex-1; + currentIndex = currentIndex - 1; } function getSentenceWithIndex(className) { @@ -169,7 +193,7 @@ function getSentenceWithIndex(className) { if (node.className == "sentence") { sentence = node - for(var i = 0, len = elements.length; i < len; i++) { + for (var i = 0, len = elements.length; i < len; i++) { if (elements[i] === sentence) { currentIndex = i; break; @@ -188,7 +212,7 @@ function getSentenceWithIndex(className) { scrollToElement(sentence); - if (audioMarkClass){ + if (audioMarkClass) { removeAllClasses(audioMarkClass); } @@ -197,382 +221,207 @@ function getSentenceWithIndex(className) { return text; } -function wrappingSentencesWithinPTags(){ - currentIndex = -1; - "use strict"; - - var rxOpen = new RegExp("<[^\\/].+?>"), - rxClose = new RegExp("<\\/.+?>"), - rxSupStart = new RegExp("^]*>"), - rxSupEnd = new RegExp("<\/sup>"), - sentenceEnd = [], - rxIndex; - - sentenceEnd.push(new RegExp("[^\\d][\\.!\\?]+")); - sentenceEnd.push(new RegExp("(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*?$)")); - sentenceEnd.push(new RegExp("(?![^\\(]*?\\))")); - sentenceEnd.push(new RegExp("(?![^\\[]*?\\])")); - sentenceEnd.push(new RegExp("(?![^\\{]*?\\})")); - sentenceEnd.push(new RegExp("(?![^\\|]*?\\|)")); - sentenceEnd.push(new RegExp("(?![^\\\\]*?\\\\)")); - //sentenceEnd.push(new RegExp("(?![^\\/.]*\\/)")); // all could be a problem, but this one is problematic - - rxIndex = new RegExp(sentenceEnd.reduce(function (previousValue, currentValue) { - return previousValue + currentValue.source; - }, "")); - - function indexSentenceEnd(html) { - var index = html.search(rxIndex); - - if (index !== -1) { - index += html.match(rxIndex)[0].length - 1; - } +$(function () { + window.ssReader = Class({ + $singleton: true, - return index; - } + init: function () { + rangy.init(); - function pushSpan(array, className, string, classNameOpt) { - if (!string.match('[a-zA-Z0-9]+')) { - array.push(string); - } else { - array.push('' + string + ''); - } - } + this.highlighter = rangy.createHighlighter(); - function addSupToPrevious(html, array) { - var sup = html.search(rxSupStart), - end = 0, - last; - - if (sup !== -1) { - end = html.search(rxSupEnd); - if (end !== -1) { - last = array.pop(); - end = end + 6; - array.push(last.slice(0, -7) + html.slice(0, end) + last.slice(-7)); - } - } + this.highlighter.addClassApplier(rangy.createClassApplier("highlight_yellow", { + ignoreWhiteSpace: true, + tagNames: ["span", "a"] + })); - return html.slice(end); - } + this.highlighter.addClassApplier(rangy.createClassApplier("highlight_green", { + ignoreWhiteSpace: true, + tagNames: ["span", "a"] + })); - function paragraphIsSentence(html, array) { - var index = indexSentenceEnd(html); + this.highlighter.addClassApplier(rangy.createClassApplier("highlight_blue", { + ignoreWhiteSpace: true, + tagNames: ["span", "a"] + })); - if (index === -1 || index === html.length) { - pushSpan(array, "sentence", html, "paragraphIsSentence"); - html = ""; - } + this.highlighter.addClassApplier(rangy.createClassApplier("highlight_pink", { + ignoreWhiteSpace: true, + tagNames: ["span", "a"] + })); - return html; - } + this.highlighter.addClassApplier(rangy.createClassApplier("highlight_underline", { + ignoreWhiteSpace: true, + tagNames: ["span", "a"] + })); - function paragraphNoMarkup(html, array) { - var open = html.search(rxOpen), - index = 0; + }, - if (open === -1) { - index = indexSentenceEnd(html); - if (index === -1) { - index = html.length; - } + setFontAndada: function () { + this.setFont("andada"); + }, - pushSpan(array, "sentence", html.slice(0, index += 1), "paragraphNoMarkup"); - } + setFontLato: function () { + this.setFont("lato"); + }, - return html.slice(index); - } + setFontPtSerif: function () { + this.setFont("pt-serif"); + }, - function sentenceUncontained(html, array) { - var open = html.search(rxOpen), - index = 0, - close; + setFontPtSans: function () { + this.setFont("pt-sans"); + }, - if (open !== -1) { - index = indexSentenceEnd(html); - if (index === -1) { - index = html.length; - } + base64encode: function (str) { + return btoa(unescape(encodeURIComponent(str))); + }, - close = html.search(rxClose); - if (index < open || index > close) { - pushSpan(array, "sentence", html.slice(0, index += 1), "sentenceUncontained"); - } else { - index = 0; - } - } - - return html.slice(index); - } + base64decode: function (str) { + return decodeURIComponent(escape(atob(str))); + }, - function sentenceContained(html, array) { - var open = html.search(rxOpen), - index = 0, - close, - count; - - if (open !== -1) { - index = indexSentenceEnd(html); - if (index === -1) { - index = html.length; + clearSelection: function () { + if (window.getSelection) { + if (window.getSelection().empty) { // Chrome + window.getSelection().empty(); + } else if (window.getSelection().removeAllRanges) { // Firefox + window.getSelection().removeAllRanges(); + } + } else if (document.selection) { // IE? + document.selection.empty(); } + }, - close = html.search(rxClose); - if (index > open && index < close) { - count = html.match(rxClose)[0].length; - pushSpan(array, "sentence", html.slice(0, close + count), "sentenceContained"); - index = close + count; - } else { - index = 0; - } - } + // Public methods - return html.slice(index); - } + setFont: function (fontName) { + $("#ss-wrapper-font").removeClass().addClass("ss-wrapper-" + fontName); + }, - function anythingElse(html, array) { - pushSpan(array, "sentence", html, "anythingElse"); + setSize: function (size) { + $("#ss-wrapper-size").removeClass().addClass("ss-wrapper-" + size); + }, - return ""; - } + setTheme: function (theme) { + $("body, #ss-wrapper-theme").removeClass().addClass("ss-wrapper-" + theme); + }, - function guessSenetences() { - var paragraphs = document.getElementsByTagName("p"); - - Array.prototype.forEach.call(paragraphs, function (paragraph) { - var html = paragraph.innerHTML, - length = html.length, - array = [], - safety = 100; - - while (length && safety) { - html = addSupToPrevious(html, array); - if (html.length === length) { - if (html.length === length) { - html = paragraphIsSentence(html, array); - if (html.length === length) { - html = paragraphNoMarkup(html, array); - if (html.length === length) { - html = sentenceUncontained(html, array); - if (html.length === length) { - html = sentenceContained(html, array); - if (html.length === length) { - html = anythingElse(html, array); - } - } - } - } - } - } + setComment: function (comment, inputId) { + $("#" + inputId).val(ssReader.base64decode(comment)); + $("#" + inputId).trigger("input", ["true"]); + }, - length = html.length; - safety -= 1; + highlightSelection: function (color) { + try { + + this.highlighter.highlightSelection(color, null); + var range = window.getSelection().toString(); + var params = {content: range, rangy: this.getHighlights(), color: color}; + this.clearSelection(); + Highlight.onReceiveHighlights(JSON.stringify(params)); + } catch (err) { + console.log("highlightSelection : " + err); } + }, + unHighlightSelection: function () { try { - paragraph.innerHTML = array.join(""); - } catch(err) { - console.error(err); - console.error("-> " + err.message); + this.highlighter.unhighlightSelection(); + Highlight.onReceiveHighlights(this.getHighlights()); + } catch (err) { } - }); - } - - guessSenetences(); -} - -$(function(){ - window.ssReader = Class({ - $singleton: true, - - init: function() { - rangy.init(); - - this.highlighter = rangy.createHighlighter(); - - this.highlighter.addClassApplier(rangy.createClassApplier("highlight_yellow", { - ignoreWhiteSpace: true, - tagNames: ["span", "a"] - })); - - this.highlighter.addClassApplier(rangy.createClassApplier("highlight_green", { - ignoreWhiteSpace: true, - tagNames: ["span", "a"] - })); - - this.highlighter.addClassApplier(rangy.createClassApplier("highlight_blue", { - ignoreWhiteSpace: true, - tagNames: ["span", "a"] - })); + }, - this.highlighter.addClassApplier(rangy.createClassApplier("highlight_pink", { - ignoreWhiteSpace: true, - tagNames: ["span", "a"] - })); - - this.highlighter.addClassApplier(rangy.createClassApplier("highlight_underline", { - ignoreWhiteSpace: true, - tagNames: ["span", "a"] - })); - - }, - - setFontAndada: function(){ - this.setFont("andada"); - }, - - setFontLato: function(){ - this.setFont("lato"); - }, + getHighlights: function () { + try { + return this.highlighter.serialize(); + } catch (err) { + } + }, - setFontPtSerif: function(){ - this.setFont("pt-serif"); - }, + setHighlights: function (serializedHighlight) { + try { + this.highlighter.removeAllHighlights(); + this.highlighter.deserialize(serializedHighlight); + } catch (err) { + } + }, - setFontPtSans: function(){ - this.setFont("pt-sans"); - }, + removeAll: function () { + try { + this.highlighter.removeAllHighlights(); + } catch (err) { + } + }, - base64encode: function(str){ - return btoa(unescape(encodeURIComponent(str))); - }, + copy: function () { + SSBridge.onCopy(window.getSelection().toString()); + this.clearSelection(); + }, - base64decode: function(str){ - return decodeURIComponent(escape(atob(str))); - }, + share: function () { + SSBridge.onShare(window.getSelection().toString()); + this.clearSelection(); + }, - clearSelection: function(){ - if (window.getSelection) { - if (window.getSelection().empty) { // Chrome - window.getSelection().empty(); - } else if (window.getSelection().removeAllRanges) { // Firefox - window.getSelection().removeAllRanges(); + search: function () { + SSBridge.onSearch(window.getSelection().toString()); + this.clearSelection(); } - } else if (document.selection) { // IE? - document.selection.empty(); - } - }, - - // Public methods - - setFont: function(fontName){ - $("#ss-wrapper-font").removeClass().addClass("ss-wrapper-"+fontName); - }, - - setSize: function(size){ - $("#ss-wrapper-size").removeClass().addClass("ss-wrapper-"+size); - }, - - setTheme: function(theme){ - $("body, #ss-wrapper-theme").removeClass().addClass("ss-wrapper-"+theme); - }, - - setComment: function(comment, inputId){ - $("#"+inputId).val(ssReader.base64decode(comment)); - $("#"+inputId).trigger("input", ["true"]); - }, - - highlightSelection: function(color){ - try { - - this.highlighter.highlightSelection(color, null); - var range = window.getSelection().toString(); - var params = {content: range,rangy: this.getHighlights(),color: color}; - this.clearSelection(); - Highlight.onReceiveHighlights(JSON.stringify(params)); - } catch(err){ - console.log("highlightSelection : " + err); - } - }, - - unHighlightSelection: function(){ - try { - this.highlighter.unhighlightSelection(); - Highlight.onReceiveHighlights(this.getHighlights()); - } catch(err){} - }, - - getHighlights: function(){ - try { - return this.highlighter.serialize(); - } catch(err){} - }, - - setHighlights: function(serializedHighlight){ - try { - this.highlighter.removeAllHighlights(); - this.highlighter.deserialize(serializedHighlight); - } catch(err){} - }, - - removeAll: function(){ - try { - this.highlighter.removeAllHighlights(); - } catch(err){} - }, - - copy: function(){ - SSBridge.onCopy(window.getSelection().toString()); - this.clearSelection(); - }, - - share: function(){ - SSBridge.onShare(window.getSelection().toString()); - this.clearSelection(); - }, - - search: function(){ - SSBridge.onSearch(window.getSelection().toString()); - this.clearSelection(); - } - }); + }); - if(typeof ssReader !== "undefined"){ - ssReader.init(); + if (typeof ssReader !== "undefined") { + ssReader.init(); } - $(".verse").click(function(){ - SSBridge.onVerseClick(ssReader.base64encode($(this).attr("verse"))); + $(".verse").click(function () { + SSBridge.onVerseClick(ssReader.base64encode($(this).attr("verse"))); }); - $("code").each(function(i){ - var textarea = $(""; + support.noCloneChecked = !!div.cloneNode(true).lastChild.defaultValue; + })(); + var documentElement = document.documentElement; + + var rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + // Support: IE <=9 only + // See #13393 for more info + function safeActiveElement() { + try { + return document.activeElement; + } catch (err) {} + } + + function on(elem, types, selector, data, fn, one) { + var origFn, type; + + // Types can be a map of types/handlers + if (typeof types === "object") { + + // ( types-Object, selector, data ) + if (typeof selector !== "string") { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for (type in types) { + on(elem, type, selector, data, types[type], one); + } + return elem; + } + + if (data == null && fn == null) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if (fn == null) { + if (typeof selector === "string") { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if (fn === false) { + fn = returnFalse; + } else if (!fn) { + return elem; + } + + if (one === 1) { + origFn = fn; + fn = function (event) { + + // Can use an empty set, since event contains the info + jQuery().off(event); + return origFn.apply(this, arguments); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); + } + return elem.each(function () { + jQuery.event.add(this, types, fn, data, selector); + }); + } + + /* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ + jQuery.event = { + + global: {}, + + add: function (elem, types, handler, data, selector) { + + var handleObjIn, + eventHandle, + tmp, + events, + t, + handleObj, + special, + handlers, + type, + namespaces, + origType, + elemData = dataPriv.get(elem); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if (!elemData) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if (handler.handler) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if (selector) { + jQuery.find.matchesSelector(documentElement, selector); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if (!handler.guid) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if (!(events = elemData.events)) { + events = elemData.events = {}; + } + if (!(eventHandle = elemData.handle)) { + eventHandle = elemData.handle = function (e) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply(elem, arguments) : undefined; + }; + } + + // Handle multiple events separated by a space + types = (types || "").match(rnothtmlwhite) || [""]; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || "").split(".").sort(); + + // There *must* be a type, no attaching namespace-only handlers + if (!type) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[type] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = (selector ? special.delegateType : special.bindType) || type; + + // Update special based on newly reset type + special = jQuery.event.special[type] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test(selector), + namespace: namespaces.join(".") + }, handleObjIn); + + // Init the event handler queue if we're the first + if (!(handlers = events[type])) { + handlers = events[type] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { + + if (elem.addEventListener) { + elem.addEventListener(type, eventHandle); + } + } + } + + if (special.add) { + special.add.call(elem, handleObj); + + if (!handleObj.handler.guid) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if (selector) { + handlers.splice(handlers.delegateCount++, 0, handleObj); + } else { + handlers.push(handleObj); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[type] = true; + } + }, + + // Detach an event or set of events from an element + remove: function (elem, types, handler, selector, mappedTypes) { + + var j, + origCount, + tmp, + events, + t, + handleObj, + special, + handlers, + type, + namespaces, + origType, + elemData = dataPriv.hasData(elem) && dataPriv.get(elem); + + if (!elemData || !(events = elemData.events)) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = (types || "").match(rnothtmlwhite) || [""]; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || "").split(".").sort(); + + // Unbind all events (on this namespace, if provided) for the element + if (!type) { + for (type in events) { + jQuery.event.remove(elem, type + types[t], handler, selector, true); + } + continue; + } + + special = jQuery.event.special[type] || {}; + type = (selector ? special.delegateType : special.bindType) || type; + handlers = events[type] || []; + tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); + + // Remove matching events + origCount = j = handlers.length; + while (j--) { + handleObj = handlers[j]; + + if ((mappedTypes || origType === handleObj.origType) && (!handler || handler.guid === handleObj.guid) && (!tmp || tmp.test(handleObj.namespace)) && (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) { + handlers.splice(j, 1); + + if (handleObj.selector) { + handlers.delegateCount--; + } + if (special.remove) { + special.remove.call(elem, handleObj); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if (origCount && !handlers.length) { + if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) { + + jQuery.removeEvent(elem, type, elemData.handle); + } + + delete events[type]; + } + } + + // Remove data and the expando if it's no longer used + if (jQuery.isEmptyObject(events)) { + dataPriv.remove(elem, "handle events"); + } + }, + + dispatch: function (nativeEvent) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix(nativeEvent); + + var i, + j, + ret, + matched, + handleObj, + handlerQueue, + args = new Array(arguments.length), + handlers = (dataPriv.get(this, "events") || {})[event.type] || [], + special = jQuery.event.special[event.type] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + + for (i = 1; i < arguments.length; i++) { + args[i] = arguments[i]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if (special.preDispatch && special.preDispatch.call(this, event) === false) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call(this, event, handlers); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { + event.currentTarget = matched.elem; + + j = 0; + while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if (!event.rnamespace || event.rnamespace.test(handleObj.namespace)) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args); + + if (ret !== undefined) { + if ((event.result = ret) === false) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if (special.postDispatch) { + special.postDispatch.call(this, event); + } + + return event.result; + }, + + handlers: function (event, handlers) { + var i, + handleObj, + sel, + matchedHandlers, + matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if (delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !(event.type === "click" && event.button >= 1)) { + + for (; cur !== this; cur = cur.parentNode || this) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if (cur.nodeType === 1 && !(event.type === "click" && cur.disabled === true)) { + matchedHandlers = []; + matchedSelectors = {}; + for (i = 0; i < delegateCount; i++) { + handleObj = handlers[i]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if (matchedSelectors[sel] === undefined) { + matchedSelectors[sel] = handleObj.needsContext ? jQuery(sel, this).index(cur) > -1 : jQuery.find(sel, this, null, [cur]).length; + } + if (matchedSelectors[sel]) { + matchedHandlers.push(handleObj); + } + } + if (matchedHandlers.length) { + handlerQueue.push({ elem: cur, handlers: matchedHandlers }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if (delegateCount < handlers.length) { + handlerQueue.push({ elem: cur, handlers: handlers.slice(delegateCount) }); + } + + return handlerQueue; + }, + + addProp: function (name, hook) { + Object.defineProperty(jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction(hook) ? function () { + if (this.originalEvent) { + return hook(this.originalEvent); + } + } : function () { + if (this.originalEvent) { + return this.originalEvent[name]; + } + }, + + set: function (value) { + Object.defineProperty(this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }); + } + }); + }, + + fix: function (originalEvent) { + return originalEvent[jQuery.expando] ? originalEvent : new jQuery.Event(originalEvent); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function () { + if (this !== safeActiveElement() && this.focus) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function () { + if (this === safeActiveElement() && this.blur) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function () { + if (this.type === "checkbox" && this.click && nodeName(this, "input")) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function (event) { + return nodeName(event.target, "a"); + } + }, + + beforeunload: { + postDispatch: function (event) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if (event.result !== undefined && event.originalEvent) { + event.originalEvent.returnValue = event.result; + } + } + } + } + }; + + jQuery.removeEvent = function (elem, type, handle) { + + // This "if" is needed for plain objects + if (elem.removeEventListener) { + elem.removeEventListener(type, handle); + } + }; + + jQuery.Event = function (src, props) { + + // Allow instantiation without the 'new' keyword + if (!(this instanceof jQuery.Event)) { + return new jQuery.Event(src, props); + } + + // Event object + if (src && src.type) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? returnTrue : returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = src.target && src.target.nodeType === 3 ? src.target.parentNode : src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if (props) { + jQuery.extend(this, props); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[jQuery.expando] = true; + }; + + // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding + // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function () { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if (e && !this.isSimulated) { + e.preventDefault(); + } + }, + stopPropagation: function () { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if (e && !this.isSimulated) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function () { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if (e && !this.isSimulated) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } + }; + + // Includes all common event props including KeyEvent and MouseEvent specific props + jQuery.each({ + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function (event) { + var button = event.button; + + // Add which for key events + if (event.which == null && rkeyEvent.test(event.type)) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if (!event.which && button !== undefined && rmouseEvent.test(event.type)) { + if (button & 1) { + return 1; + } + + if (button & 2) { + return 3; + } + + if (button & 4) { + return 2; + } + + return 0; + } + + return event.which; + } + }, jQuery.event.addProp); + + // Create mouseenter/leave events using mouseover/out and event-time checks + // so that event delegation works in jQuery. + // Do the same for pointerenter/pointerleave and pointerover/pointerout + // + // Support: Safari 7 only + // Safari sends mouseenter too often; see: + // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 + // for the description of the bug (it existed in older Chrome versions as well). + jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" + }, function (orig, fix) { + jQuery.event.special[orig] = { + delegateType: fix, + bindType: fix, + + handle: function (event) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if (!related || related !== target && !jQuery.contains(target, related)) { + event.type = handleObj.origType; + ret = handleObj.handler.apply(this, arguments); + event.type = fix; + } + return ret; + } + }; + }); + + jQuery.fn.extend({ + + on: function (types, selector, data, fn) { + return on(this, types, selector, data, fn); + }, + one: function (types, selector, data, fn) { + return on(this, types, selector, data, fn, 1); + }, + off: function (types, selector, fn) { + var handleObj, type; + if (types && types.preventDefault && types.handleObj) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery(types.delegateTarget).off(handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler); + return this; + } + if (typeof types === "object") { + + // ( types-object [, selector] ) + for (type in types) { + this.off(type, selector, types[type]); + } + return this; + } + if (selector === false || typeof selector === "function") { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if (fn === false) { + fn = returnFalse; + } + return this.each(function () { + jQuery.event.remove(this, types, fn, selector); + }); + } + }); + + var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + + // Prefer a tbody over its parent table for containing new rows + function manipulationTarget(elem, content) { + if (nodeName(elem, "table") && nodeName(content.nodeType !== 11 ? content : content.firstChild, "tr")) { + + return jQuery(elem).children("tbody")[0] || elem; + } + + return elem; + } + + // Replace/restore the type attribute of script elements for safe DOM manipulation + function disableScript(elem) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; + } + function restoreScript(elem) { + if ((elem.type || "").slice(0, 5) === "true/") { + elem.type = elem.type.slice(5); + } else { + elem.removeAttribute("type"); + } + + return elem; + } + + function cloneCopyEvent(src, dest) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if (dest.nodeType !== 1) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if (dataPriv.hasData(src)) { + pdataOld = dataPriv.access(src); + pdataCur = dataPriv.set(dest, pdataOld); + events = pdataOld.events; + + if (events) { + delete pdataCur.handle; + pdataCur.events = {}; + + for (type in events) { + for (i = 0, l = events[type].length; i < l; i++) { + jQuery.event.add(dest, type, events[type][i]); + } + } + } + } + + // 2. Copy user data + if (dataUser.hasData(src)) { + udataOld = dataUser.access(src); + udataCur = jQuery.extend({}, udataOld); + + dataUser.set(dest, udataCur); + } + } + + // Fix IE bugs, see support tests + function fixInput(src, dest) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if (nodeName === "input" && rcheckableType.test(src.type)) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if (nodeName === "input" || nodeName === "textarea") { + dest.defaultValue = src.defaultValue; + } + } + + function domManip(collection, args, callback, ignored) { + + // Flatten any nested arrays + args = concat.apply([], args); + + var fragment, + first, + scripts, + hasScripts, + node, + doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[0], + valueIsFunction = isFunction(value); + + // We can't cloneNode fragments that contain checked, in WebKit + if (valueIsFunction || l > 1 && typeof value === "string" && !support.checkClone && rchecked.test(value)) { + return collection.each(function (index) { + var self = collection.eq(index); + if (valueIsFunction) { + args[0] = value.call(this, index, self.html()); + } + domManip(self, args, callback, ignored); + }); + } + + if (l) { + fragment = buildFragment(args, collection[0].ownerDocument, false, collection, ignored); + first = fragment.firstChild; + + if (fragment.childNodes.length === 1) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if (first || ignored) { + scripts = jQuery.map(getAll(fragment, "script"), disableScript); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for (; i < l; i++) { + node = fragment; + + if (i !== iNoClone) { + node = jQuery.clone(node, true, true); + + // Keep references to cloned scripts for later restoration + if (hasScripts) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge(scripts, getAll(node, "script")); + } + } + + callback.call(collection[i], node, i); + } + + if (hasScripts) { + doc = scripts[scripts.length - 1].ownerDocument; + + // Reenable scripts + jQuery.map(scripts, restoreScript); + + // Evaluate executable scripts on first document insertion + for (i = 0; i < hasScripts; i++) { + node = scripts[i]; + if (rscriptType.test(node.type || "") && !dataPriv.access(node, "globalEval") && jQuery.contains(doc, node)) { + + if (node.src && (node.type || "").toLowerCase() !== "module") { + + // Optional AJAX dependency, but won't run scripts if not present + if (jQuery._evalUrl) { + jQuery._evalUrl(node.src); + } + } else { + DOMEval(node.textContent.replace(rcleanScript, ""), doc, node); + } + } + } + } + } + } + + return collection; + } + + function remove(elem, selector, keepData) { + var node, + nodes = selector ? jQuery.filter(selector, elem) : elem, + i = 0; + + for (; (node = nodes[i]) != null; i++) { + if (!keepData && node.nodeType === 1) { + jQuery.cleanData(getAll(node)); + } + + if (node.parentNode) { + if (keepData && jQuery.contains(node.ownerDocument, node)) { + setGlobalEval(getAll(node, "script")); + } + node.parentNode.removeChild(node); + } + } + + return elem; + } + + jQuery.extend({ + htmlPrefilter: function (html) { + return html.replace(rxhtmlTag, "<$1>"); + }, + + clone: function (elem, dataAndEvents, deepDataAndEvents) { + var i, + l, + srcElements, + destElements, + clone = elem.cloneNode(true), + inPage = jQuery.contains(elem.ownerDocument, elem); + + // Fix IE cloning issues + if (!support.noCloneChecked && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem)) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll(clone); + srcElements = getAll(elem); + + for (i = 0, l = srcElements.length; i < l; i++) { + fixInput(srcElements[i], destElements[i]); + } + } + + // Copy the events from the original to the clone + if (dataAndEvents) { + if (deepDataAndEvents) { + srcElements = srcElements || getAll(elem); + destElements = destElements || getAll(clone); + + for (i = 0, l = srcElements.length; i < l; i++) { + cloneCopyEvent(srcElements[i], destElements[i]); + } + } else { + cloneCopyEvent(elem, clone); + } + } + + // Preserve script evaluation history + destElements = getAll(clone, "script"); + if (destElements.length > 0) { + setGlobalEval(destElements, !inPage && getAll(elem, "script")); + } + + // Return the cloned set + return clone; + }, + + cleanData: function (elems) { + var data, + elem, + type, + special = jQuery.event.special, + i = 0; + + for (; (elem = elems[i]) !== undefined; i++) { + if (acceptData(elem)) { + if (data = elem[dataPriv.expando]) { + if (data.events) { + for (type in data.events) { + if (special[type]) { + jQuery.event.remove(elem, type); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent(elem, type, data.handle); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[dataPriv.expando] = undefined; + } + if (elem[dataUser.expando]) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[dataUser.expando] = undefined; + } + } + } + } + }); + + jQuery.fn.extend({ + detach: function (selector) { + return remove(this, selector, true); + }, + + remove: function (selector) { + return remove(this, selector); + }, + + text: function (value) { + return access(this, function (value) { + return value === undefined ? jQuery.text(this) : this.empty().each(function () { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + this.textContent = value; + } + }); + }, null, value, arguments.length); + }, + + append: function () { + return domManip(this, arguments, function (elem) { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + var target = manipulationTarget(this, elem); + target.appendChild(elem); + } + }); + }, + + prepend: function () { + return domManip(this, arguments, function (elem) { + if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { + var target = manipulationTarget(this, elem); + target.insertBefore(elem, target.firstChild); + } + }); + }, + + before: function () { + return domManip(this, arguments, function (elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this); + } + }); + }, + + after: function () { + return domManip(this, arguments, function (elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this.nextSibling); + } + }); + }, + + empty: function () { + var elem, + i = 0; + + for (; (elem = this[i]) != null; i++) { + if (elem.nodeType === 1) { + + // Prevent memory leaks + jQuery.cleanData(getAll(elem, false)); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function (dataAndEvents, deepDataAndEvents) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function () { + return jQuery.clone(this, dataAndEvents, deepDataAndEvents); + }); + }, + + html: function (value) { + return access(this, function (value) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if (value === undefined && elem.nodeType === 1) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if (typeof value === "string" && !rnoInnerhtml.test(value) && !wrapMap[(rtagName.exec(value) || ["", ""])[1].toLowerCase()]) { + + value = jQuery.htmlPrefilter(value); + + try { + for (; i < l; i++) { + elem = this[i] || {}; + + // Remove element nodes and prevent memory leaks + if (elem.nodeType === 1) { + jQuery.cleanData(getAll(elem, false)); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch (e) {} + } + + if (elem) { + this.empty().append(value); + } + }, null, value, arguments.length); + }, + + replaceWith: function () { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip(this, arguments, function (elem) { + var parent = this.parentNode; + + if (jQuery.inArray(this, ignored) < 0) { + jQuery.cleanData(getAll(this)); + if (parent) { + parent.replaceChild(elem, this); + } + } + + // Force callback invocation + }, ignored); + } + }); + + jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function (name, original) { + jQuery.fn[name] = function (selector) { + var elems, + ret = [], + insert = jQuery(selector), + last = insert.length - 1, + i = 0; + + for (; i <= last; i++) { + elems = i === last ? this : this.clone(true); + jQuery(insert[i])[original](elems); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply(ret, elems.get()); + } + + return this.pushStack(ret); + }; + }); + var rnumnonpx = new RegExp("^(" + pnum + ")(?!px)[a-z%]+$", "i"); + + var getStyles = function (elem) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if (!view || !view.opener) { + view = window; + } + + return view.getComputedStyle(elem); + }; + + var rboxStyle = new RegExp(cssExpand.join("|"), "i"); + + (function () { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if (!div) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + "margin-top:1px;padding:0;border:0"; + div.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + "width:60%;top:1%"; + documentElement.appendChild(container).appendChild(div); + + var divStyle = window.getComputedStyle(div); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures(divStyle.marginLeft) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures(divStyle.right) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures(divStyle.width) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + div.style.position = "absolute"; + scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; + + documentElement.removeChild(container); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures(measure) { + return Math.round(parseFloat(measure)); + } + + var pixelPositionVal, + boxSizingReliableVal, + scrollboxSizeVal, + pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement("div"), + div = document.createElement("div"); + + // Finish early in limited (non-browser) environments + if (!div.style) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode(true).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend(support, { + boxSizingReliable: function () { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function () { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function () { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function () { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function () { + computeStyleTests(); + return scrollboxSizeVal; + } + }); + })(); + + function curCSS(elem, name, computed) { + var width, + minWidth, + maxWidth, + ret, + + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles(elem); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if (computed) { + ret = computed.getPropertyValue(name) || computed[name]; + + if (ret === "" && !jQuery.contains(elem.ownerDocument, elem)) { + ret = jQuery.style(elem, name); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if (!support.pixelBoxStyles() && rnumnonpx.test(ret) && rboxStyle.test(name)) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : ret; + } + + function addGetHookIf(conditionFn, hookFn) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function () { + if (conditionFn()) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return (this.get = hookFn).apply(this, arguments); + } + }; + } + + var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + cssPrefixes = ["Webkit", "Moz", "ms"], + emptyStyle = document.createElement("div").style; + + // Return a css property mapped to a potentially vendor prefixed property + function vendorPropName(name) { + + // Shortcut for names that are not vendor prefixed + if (name in emptyStyle) { + return name; + } + + // Check for vendor prefixed names + var capName = name[0].toUpperCase() + name.slice(1), + i = cssPrefixes.length; + + while (i--) { + name = cssPrefixes[i] + capName; + if (name in emptyStyle) { + return name; + } + } + } + + // Return a property mapped along what jQuery.cssProps suggests or to + // a vendor prefixed property. + function finalPropName(name) { + var ret = jQuery.cssProps[name]; + if (!ret) { + ret = jQuery.cssProps[name] = vendorPropName(name) || name; + } + return ret; + } + + function setPositiveNumber(elem, value, subtract) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec(value); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max(0, matches[2] - (subtract || 0)) + (matches[3] || "px") : value; + } + + function boxModelAdjustment(elem, dimension, box, isBorderBox, styles, computedVal) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if (box === (isBorderBox ? "border" : "content")) { + return 0; + } + + for (; i < 4; i += 2) { + + // Both box models exclude margin + if (box === "margin") { + delta += jQuery.css(elem, box + cssExpand[i], true, styles); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if (!isBorderBox) { + + // Add padding + delta += jQuery.css(elem, "padding" + cssExpand[i], true, styles); + + // For "border" or "margin", add border + if (box !== "padding") { + delta += jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + + // But still keep track of it otherwise + } else { + extra += jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if (box === "content") { + delta -= jQuery.css(elem, "padding" + cssExpand[i], true, styles); + } + + // For "content" or "padding", subtract border + if (box !== "margin") { + delta -= jQuery.css(elem, "border" + cssExpand[i] + "Width", true, styles); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if (!isBorderBox && computedVal >= 0) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max(0, Math.ceil(elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)] - computedVal - delta - extra - 0.5)); + } + + return delta; + } + + function getWidthOrHeight(elem, dimension, extra) { + + // Start with computed style + var styles = getStyles(elem), + val = curCSS(elem, dimension, styles), + isBorderBox = jQuery.css(elem, "boxSizing", false, styles) === "border-box", + valueIsBorderBox = isBorderBox; + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if (rnumnonpx.test(val)) { + if (!extra) { + return val; + } + val = "auto"; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = valueIsBorderBox && (support.boxSizingReliable() || val === elem.style[dimension]); + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + if (val === "auto" || !parseFloat(val) && jQuery.css(elem, "display", false, styles) === "inline") { + + val = elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)]; + + // offsetWidth/offsetHeight provide border-box values + valueIsBorderBox = true; + } + + // Normalize "" and auto + val = parseFloat(val) || 0; + + // Adjust for the element's box model + return val + boxModelAdjustment(elem, dimension, extra || (isBorderBox ? "border" : "content"), valueIsBorderBox, styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val) + "px"; + } + + jQuery.extend({ + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function (elem, computed) { + if (computed) { + + // We should always get a number back from opacity + var ret = curCSS(elem, "opacity"); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function (elem, name, value, extra) { + + // Don't set styles on text and comment nodes + if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) { + return; + } + + // Make sure that we're working with the right name + var ret, + type, + hooks, + origName = camelCase(name), + isCustomProp = rcustomProp.test(name), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if (!isCustomProp) { + name = finalPropName(origName); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // Check if we're setting a value + if (value !== undefined) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if (type === "string" && (ret = rcssNum.exec(value)) && ret[1]) { + value = adjustCSS(elem, name, ret); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if (value == null || value !== value) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if (type === "number") { + value += ret && ret[3] || (jQuery.cssNumber[origName] ? "" : "px"); + } + + // background-* props affect original clone's values + if (!support.clearCloneStyle && value === "" && name.indexOf("background") === 0) { + style[name] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if (!hooks || !("set" in hooks) || (value = hooks.set(elem, value, extra)) !== undefined) { + + if (isCustomProp) { + style.setProperty(name, value); + } else { + style[name] = value; + } + } + } else { + + // If a hook was provided get the non-computed value from there + if (hooks && "get" in hooks && (ret = hooks.get(elem, false, extra)) !== undefined) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[name]; + } + }, + + css: function (elem, name, extra, styles) { + var val, + num, + hooks, + origName = camelCase(name), + isCustomProp = rcustomProp.test(name); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if (!isCustomProp) { + name = finalPropName(origName); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // If a hook was provided get the computed value from there + if (hooks && "get" in hooks) { + val = hooks.get(elem, true, extra); + } + + // Otherwise, if a way to get the computed value exists, use that + if (val === undefined) { + val = curCSS(elem, name, styles); + } + + // Convert "normal" to computed value + if (val === "normal" && name in cssNormalTransform) { + val = cssNormalTransform[name]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if (extra === "" || extra) { + num = parseFloat(val); + return extra === true || isFinite(num) ? num || 0 : val; + } + + return val; + } + }); + + jQuery.each(["height", "width"], function (i, dimension) { + jQuery.cssHooks[dimension] = { + get: function (elem, computed, extra) { + if (computed) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test(jQuery.css(elem, "display")) && ( + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + !elem.getClientRects().length || !elem.getBoundingClientRect().width) ? swap(elem, cssShow, function () { + return getWidthOrHeight(elem, dimension, extra); + }) : getWidthOrHeight(elem, dimension, extra); + } + }, + + set: function (elem, value, extra) { + var matches, + styles = getStyles(elem), + isBorderBox = jQuery.css(elem, "boxSizing", false, styles) === "border-box", + subtract = extra && boxModelAdjustment(elem, dimension, extra, isBorderBox, styles); + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if (isBorderBox && support.scrollboxSize() === styles.position) { + subtract -= Math.ceil(elem["offset" + dimension[0].toUpperCase() + dimension.slice(1)] - parseFloat(styles[dimension]) - boxModelAdjustment(elem, dimension, "border", false, styles) - 0.5); + } + + // Convert to pixels if value adjustment is needed + if (subtract && (matches = rcssNum.exec(value)) && (matches[3] || "px") !== "px") { + + elem.style[dimension] = value; + value = jQuery.css(elem, dimension); + } + + return setPositiveNumber(elem, value, subtract); + } + }; + }); + + jQuery.cssHooks.marginLeft = addGetHookIf(support.reliableMarginLeft, function (elem, computed) { + if (computed) { + return (parseFloat(curCSS(elem, "marginLeft")) || elem.getBoundingClientRect().left - swap(elem, { marginLeft: 0 }, function () { + return elem.getBoundingClientRect().left; + })) + "px"; + } + }); + + // These hooks are used by animate to expand properties + jQuery.each({ + margin: "", + padding: "", + border: "Width" + }, function (prefix, suffix) { + jQuery.cssHooks[prefix + suffix] = { + expand: function (value) { + var i = 0, + expanded = {}, + + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split(" ") : [value]; + + for (; i < 4; i++) { + expanded[prefix + cssExpand[i] + suffix] = parts[i] || parts[i - 2] || parts[0]; + } + + return expanded; + } + }; + + if (prefix !== "margin") { + jQuery.cssHooks[prefix + suffix].set = setPositiveNumber; + } + }); + + jQuery.fn.extend({ + css: function (name, value) { + return access(this, function (elem, name, value) { + var styles, + len, + map = {}, + i = 0; + + if (Array.isArray(name)) { + styles = getStyles(elem); + len = name.length; + + for (; i < len; i++) { + map[name[i]] = jQuery.css(elem, name[i], false, styles); + } + + return map; + } + + return value !== undefined ? jQuery.style(elem, name, value) : jQuery.css(elem, name); + }, name, value, arguments.length > 1); + } + }); + + function Tween(elem, options, prop, end, easing) { + return new Tween.prototype.init(elem, options, prop, end, easing); + } + jQuery.Tween = Tween; + + Tween.prototype = { + constructor: Tween, + init: function (elem, options, prop, end, easing, unit) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || (jQuery.cssNumber[prop] ? "" : "px"); + }, + cur: function () { + var hooks = Tween.propHooks[this.prop]; + + return hooks && hooks.get ? hooks.get(this) : Tween.propHooks._default.get(this); + }, + run: function (percent) { + var eased, + hooks = Tween.propHooks[this.prop]; + + if (this.options.duration) { + this.pos = eased = jQuery.easing[this.easing](percent, this.options.duration * percent, 0, 1, this.options.duration); + } else { + this.pos = eased = percent; + } + this.now = (this.end - this.start) * eased + this.start; + + if (this.options.step) { + this.options.step.call(this.elem, this.now, this); + } + + if (hooks && hooks.set) { + hooks.set(this); + } else { + Tween.propHooks._default.set(this); + } + return this; + } + }; + + Tween.prototype.init.prototype = Tween.prototype; + + Tween.propHooks = { + _default: { + get: function (tween) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if (tween.elem.nodeType !== 1 || tween.elem[tween.prop] != null && tween.elem.style[tween.prop] == null) { + return tween.elem[tween.prop]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css(tween.elem, tween.prop, ""); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function (tween) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if (jQuery.fx.step[tween.prop]) { + jQuery.fx.step[tween.prop](tween); + } else if (tween.elem.nodeType === 1 && (tween.elem.style[jQuery.cssProps[tween.prop]] != null || jQuery.cssHooks[tween.prop])) { + jQuery.style(tween.elem, tween.prop, tween.now + tween.unit); + } else { + tween.elem[tween.prop] = tween.now; + } + } + } + }; + + // Support: IE <=9 only + // Panic based approach to setting things on disconnected nodes + Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function (tween) { + if (tween.elem.nodeType && tween.elem.parentNode) { + tween.elem[tween.prop] = tween.now; + } + } + }; + + jQuery.easing = { + linear: function (p) { + return p; + }, + swing: function (p) { + return 0.5 - Math.cos(p * Math.PI) / 2; + }, + _default: "swing" + }; + + jQuery.fx = Tween.prototype.init; + + // Back compat <1.8 extension point + jQuery.fx.step = {}; + + var fxNow, + inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + + function schedule() { + if (inProgress) { + if (document.hidden === false && window.requestAnimationFrame) { + window.requestAnimationFrame(schedule); + } else { + window.setTimeout(schedule, jQuery.fx.interval); + } + + jQuery.fx.tick(); + } + } + + // Animations created synchronously will run synchronously + function createFxNow() { + window.setTimeout(function () { + fxNow = undefined; + }); + return fxNow = Date.now(); + } + + // Generate parameters to create a standard animation + function genFx(type, includeWidth) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for (; i < 4; i += 2 - includeWidth) { + which = cssExpand[i]; + attrs["margin" + which] = attrs["padding" + which] = type; + } + + if (includeWidth) { + attrs.opacity = attrs.width = type; + } + + return attrs; + } + + function createTween(value, prop, animation) { + var tween, + collection = (Animation.tweeners[prop] || []).concat(Animation.tweeners["*"]), + index = 0, + length = collection.length; + for (; index < length; index++) { + if (tween = collection[index].call(animation, prop, value)) { + + // We're done with this property + return tween; + } + } + } + + function defaultPrefilter(elem, props, opts) { + var prop, + value, + toggle, + hooks, + oldfire, + propTween, + restoreDisplay, + display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree(elem), + dataShow = dataPriv.get(elem, "fxshow"); + + // Queue-skipping animations hijack the fx hooks + if (!opts.queue) { + hooks = jQuery._queueHooks(elem, "fx"); + if (hooks.unqueued == null) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function () { + if (!hooks.unqueued) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always(function () { + + // Ensure the complete handler is called before this completes + anim.always(function () { + hooks.unqueued--; + if (!jQuery.queue(elem, "fx").length) { + hooks.empty.fire(); + } + }); + }); + } + + // Detect show/hide animations + for (prop in props) { + value = props[prop]; + if (rfxtypes.test(value)) { + delete props[prop]; + toggle = toggle || value === "toggle"; + if (value === (hidden ? "hide" : "show")) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if (value === "show" && dataShow && dataShow[prop] !== undefined) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[prop] = dataShow && dataShow[prop] || jQuery.style(elem, prop); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject(props); + if (!propTween && jQuery.isEmptyObject(orig)) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if (isBox && elem.nodeType === 1) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [style.overflow, style.overflowX, style.overflowY]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if (restoreDisplay == null) { + restoreDisplay = dataPriv.get(elem, "display"); + } + display = jQuery.css(elem, "display"); + if (display === "none") { + if (restoreDisplay) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide([elem], true); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css(elem, "display"); + showHide([elem]); + } + } + + // Animate inline elements as inline-block + if (display === "inline" || display === "inline-block" && restoreDisplay != null) { + if (jQuery.css(elem, "float") === "none") { + + // Restore the original display value at the end of pure show/hide animations + if (!propTween) { + anim.done(function () { + style.display = restoreDisplay; + }); + if (restoreDisplay == null) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if (opts.overflow) { + style.overflow = "hidden"; + anim.always(function () { + style.overflow = opts.overflow[0]; + style.overflowX = opts.overflow[1]; + style.overflowY = opts.overflow[2]; + }); + } + + // Implement show/hide animations + propTween = false; + for (prop in orig) { + + // General show/hide setup for this element animation + if (!propTween) { + if (dataShow) { + if ("hidden" in dataShow) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access(elem, "fxshow", { display: restoreDisplay }); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if (toggle) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if (hidden) { + showHide([elem], true); + } + + /* eslint-disable no-loop-func */ + + anim.done(function () { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if (!hidden) { + showHide([elem]); + } + dataPriv.remove(elem, "fxshow"); + for (prop in orig) { + jQuery.style(elem, prop, orig[prop]); + } + }); + } + + // Per-property setup + propTween = createTween(hidden ? dataShow[prop] : 0, prop, anim); + if (!(prop in dataShow)) { + dataShow[prop] = propTween.start; + if (hidden) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } + } + + function propFilter(props, specialEasing) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for (index in props) { + name = camelCase(index); + easing = specialEasing[name]; + value = props[index]; + if (Array.isArray(value)) { + easing = value[1]; + value = props[index] = value[0]; + } + + if (index !== name) { + props[name] = value; + delete props[index]; + } + + hooks = jQuery.cssHooks[name]; + if (hooks && "expand" in hooks) { + value = hooks.expand(value); + delete props[name]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for (index in value) { + if (!(index in props)) { + props[index] = value[index]; + specialEasing[index] = easing; + } + } + } else { + specialEasing[name] = easing; + } + } + } + + function Animation(elem, properties, options) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always(function () { + + // Don't match elem in the :animated selector + delete tick.elem; + }), + tick = function () { + if (stopped) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max(0, animation.startTime + animation.duration - currentTime), + + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for (; index < length; index++) { + animation.tweens[index].run(percent); + } + + deferred.notifyWith(elem, [animation, percent, remaining]); + + // If there's more to do, yield + if (percent < 1 && length) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if (!length) { + deferred.notifyWith(elem, [animation, 1, 0]); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith(elem, [animation]); + return false; + }, + animation = deferred.promise({ + elem: elem, + props: jQuery.extend({}, properties), + opts: jQuery.extend(true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function (prop, end) { + var tween = jQuery.Tween(elem, animation.opts, prop, end, animation.opts.specialEasing[prop] || animation.opts.easing); + animation.tweens.push(tween); + return tween; + }, + stop: function (gotoEnd) { + var index = 0, + + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if (stopped) { + return this; + } + stopped = true; + for (; index < length; index++) { + animation.tweens[index].run(1); + } + + // Resolve when we played the last frame; otherwise, reject + if (gotoEnd) { + deferred.notifyWith(elem, [animation, 1, 0]); + deferred.resolveWith(elem, [animation, gotoEnd]); + } else { + deferred.rejectWith(elem, [animation, gotoEnd]); + } + return this; + } + }), + props = animation.props; + + propFilter(props, animation.opts.specialEasing); + + for (; index < length; index++) { + result = Animation.prefilters[index].call(animation, elem, props, animation.opts); + if (result) { + if (isFunction(result.stop)) { + jQuery._queueHooks(animation.elem, animation.opts.queue).stop = result.stop.bind(result); + } + return result; + } + } + + jQuery.map(props, createTween, animation); + + if (isFunction(animation.opts.start)) { + animation.opts.start.call(elem, animation); + } + + // Attach callbacks from options + animation.progress(animation.opts.progress).done(animation.opts.done, animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always); + + jQuery.fx.timer(jQuery.extend(tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + })); + + return animation; + } + + jQuery.Animation = jQuery.extend(Animation, { + + tweeners: { + "*": [function (prop, value) { + var tween = this.createTween(prop, value); + adjustCSS(tween.elem, prop, rcssNum.exec(value), tween); + return tween; + }] + }, + + tweener: function (props, callback) { + if (isFunction(props)) { + callback = props; + props = ["*"]; + } else { + props = props.match(rnothtmlwhite); + } + + var prop, + index = 0, + length = props.length; + + for (; index < length; index++) { + prop = props[index]; + Animation.tweeners[prop] = Animation.tweeners[prop] || []; + Animation.tweeners[prop].unshift(callback); + } + }, + + prefilters: [defaultPrefilter], + + prefilter: function (callback, prepend) { + if (prepend) { + Animation.prefilters.unshift(callback); + } else { + Animation.prefilters.push(callback); + } + } + }); + + jQuery.speed = function (speed, easing, fn) { + var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { + complete: fn || !fn && easing || isFunction(speed) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction(easing) && easing + }; + + // Go to the end state if fx are off + if (jQuery.fx.off) { + opt.duration = 0; + } else { + if (typeof opt.duration !== "number") { + if (opt.duration in jQuery.fx.speeds) { + opt.duration = jQuery.fx.speeds[opt.duration]; + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if (opt.queue == null || opt.queue === true) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function () { + if (isFunction(opt.old)) { + opt.old.call(this); + } + + if (opt.queue) { + jQuery.dequeue(this, opt.queue); + } + }; + + return opt; + }; + + jQuery.fn.extend({ + fadeTo: function (speed, to, easing, callback) { + + // Show any hidden elements after setting opacity to 0 + return this.filter(isHiddenWithinTree).css("opacity", 0).show() + + // Animate to the value specified + .end().animate({ opacity: to }, speed, easing, callback); + }, + animate: function (prop, speed, easing, callback) { + var empty = jQuery.isEmptyObject(prop), + optall = jQuery.speed(speed, easing, callback), + doAnimation = function () { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation(this, jQuery.extend({}, prop), optall); + + // Empty animations, or finishing resolves immediately + if (empty || dataPriv.get(this, "finish")) { + anim.stop(true); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? this.each(doAnimation) : this.queue(optall.queue, doAnimation); + }, + stop: function (type, clearQueue, gotoEnd) { + var stopQueue = function (hooks) { + var stop = hooks.stop; + delete hooks.stop; + stop(gotoEnd); + }; + + if (typeof type !== "string") { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if (clearQueue && type !== false) { + this.queue(type || "fx", []); + } + + return this.each(function () { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get(this); + + if (index) { + if (data[index] && data[index].stop) { + stopQueue(data[index]); + } + } else { + for (index in data) { + if (data[index] && data[index].stop && rrun.test(index)) { + stopQueue(data[index]); + } + } + } + + for (index = timers.length; index--;) { + if (timers[index].elem === this && (type == null || timers[index].queue === type)) { + + timers[index].anim.stop(gotoEnd); + dequeue = false; + timers.splice(index, 1); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if (dequeue || !gotoEnd) { + jQuery.dequeue(this, type); + } + }); + }, + finish: function (type) { + if (type !== false) { + type = type || "fx"; + } + return this.each(function () { + var index, + data = dataPriv.get(this), + queue = data[type + "queue"], + hooks = data[type + "queueHooks"], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue(this, type, []); + + if (hooks && hooks.stop) { + hooks.stop.call(this, true); + } + + // Look for any active animations, and finish them + for (index = timers.length; index--;) { + if (timers[index].elem === this && timers[index].queue === type) { + timers[index].anim.stop(true); + timers.splice(index, 1); + } + } + + // Look for any animations in the old queue and finish them + for (index = 0; index < length; index++) { + if (queue[index] && queue[index].finish) { + queue[index].finish.call(this); + } + } + + // Turn off finishing flag + delete data.finish; + }); + } + }); + + jQuery.each(["toggle", "show", "hide"], function (i, name) { + var cssFn = jQuery.fn[name]; + jQuery.fn[name] = function (speed, easing, callback) { + return speed == null || typeof speed === "boolean" ? cssFn.apply(this, arguments) : this.animate(genFx(name, true), speed, easing, callback); + }; + }); + + // Generate shortcuts for custom animations + jQuery.each({ + slideDown: genFx("show"), + slideUp: genFx("hide"), + slideToggle: genFx("toggle"), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } + }, function (name, props) { + jQuery.fn[name] = function (speed, easing, callback) { + return this.animate(props, speed, easing, callback); + }; + }); + + jQuery.timers = []; + jQuery.fx.tick = function () { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for (; i < timers.length; i++) { + timer = timers[i]; + + // Run the timer and safely remove it when done (allowing for external removal) + if (!timer() && timers[i] === timer) { + timers.splice(i--, 1); + } + } + + if (!timers.length) { + jQuery.fx.stop(); + } + fxNow = undefined; + }; + + jQuery.fx.timer = function (timer) { + jQuery.timers.push(timer); + jQuery.fx.start(); + }; + + jQuery.fx.interval = 13; + jQuery.fx.start = function () { + if (inProgress) { + return; + } + + inProgress = true; + schedule(); + }; + + jQuery.fx.stop = function () { + inProgress = null; + }; + + jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 + }; + + // Based off of the plugin by Clint Helfers, with permission. + // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ + jQuery.fn.delay = function (time, type) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue(type, function (next, hooks) { + var timeout = window.setTimeout(next, time); + hooks.stop = function () { + window.clearTimeout(timeout); + }; + }); + }; + + (function () { + var input = document.createElement("input"), + select = document.createElement("select"), + opt = select.appendChild(document.createElement("option")); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement("input"); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; + })(); + + var boolHook, + attrHandle = jQuery.expr.attrHandle; + + jQuery.fn.extend({ + attr: function (name, value) { + return access(this, jQuery.attr, name, value, arguments.length > 1); + }, + + removeAttr: function (name) { + return this.each(function () { + jQuery.removeAttr(this, name); + }); + } + }); + + jQuery.extend({ + attr: function (elem, name, value) { + var ret, + hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if (nType === 3 || nType === 8 || nType === 2) { + return; + } + + // Fallback to prop when attributes are not supported + if (typeof elem.getAttribute === "undefined") { + return jQuery.prop(elem, name, value); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if (nType !== 1 || !jQuery.isXMLDoc(elem)) { + hooks = jQuery.attrHooks[name.toLowerCase()] || (jQuery.expr.match.bool.test(name) ? boolHook : undefined); + } + + if (value !== undefined) { + if (value === null) { + jQuery.removeAttr(elem, name); + return; + } + + if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + } + + elem.setAttribute(name, value + ""); + return value; + } + + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + } + + ret = jQuery.find.attr(elem, name); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function (elem, value) { + if (!support.radioValue && value === "radio" && nodeName(elem, "input")) { + var val = elem.value; + elem.setAttribute("type", value); + if (val) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function (elem, value) { + var name, + i = 0, + + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match(rnothtmlwhite); + + if (attrNames && elem.nodeType === 1) { + while (name = attrNames[i++]) { + elem.removeAttribute(name); + } + } + } + }); + + // Hooks for boolean attributes + boolHook = { + set: function (elem, value, name) { + if (value === false) { + + // Remove boolean attributes when set to false + jQuery.removeAttr(elem, name); + } else { + elem.setAttribute(name, name); + } + return name; + } + }; + + jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g), function (i, name) { + var getter = attrHandle[name] || jQuery.find.attr; + + attrHandle[name] = function (elem, name, isXML) { + var ret, + handle, + lowercaseName = name.toLowerCase(); + + if (!isXML) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[lowercaseName]; + attrHandle[lowercaseName] = ret; + ret = getter(elem, name, isXML) != null ? lowercaseName : null; + attrHandle[lowercaseName] = handle; + } + return ret; + }; + }); + + var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + + jQuery.fn.extend({ + prop: function (name, value) { + return access(this, jQuery.prop, name, value, arguments.length > 1); + }, + + removeProp: function (name) { + return this.each(function () { + delete this[jQuery.propFix[name] || name]; + }); + } + }); + + jQuery.extend({ + prop: function (elem, name, value) { + var ret, + hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if (nType === 3 || nType === 8 || nType === 2) { + return; + } + + if (nType !== 1 || !jQuery.isXMLDoc(elem)) { + + // Fix name and attach hooks + name = jQuery.propFix[name] || name; + hooks = jQuery.propHooks[name]; + } + + if (value !== undefined) { + if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { + return ret; + } + + return elem[name] = value; + } + + if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + } + + return elem[name]; + }, + + propHooks: { + tabIndex: { + get: function (elem) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr(elem, "tabindex"); + + if (tabindex) { + return parseInt(tabindex, 10); + } + + if (rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } + }); + + // Support: IE <=11 only + // Accessing the selectedIndex property + // forces the browser to respect setting selected + // on the option + // The getter ensures a default option is selected + // when in an optgroup + // eslint rule "no-unused-expressions" is disabled for this code + // since it considers such accessions noop + if (!support.optSelected) { + jQuery.propHooks.selected = { + get: function (elem) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if (parent && parent.parentNode) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function (elem) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if (parent) { + parent.selectedIndex; + + if (parent.parentNode) { + parent.parentNode.selectedIndex; + } + } + } + }; + } + + jQuery.each(["tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable"], function () { + jQuery.propFix[this.toLowerCase()] = this; + }); + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse(value) { + var tokens = value.match(rnothtmlwhite) || []; + return tokens.join(" "); + } + + function getClass(elem) { + return elem.getAttribute && elem.getAttribute("class") || ""; + } + + function classesToArray(value) { + if (Array.isArray(value)) { + return value; + } + if (typeof value === "string") { + return value.match(rnothtmlwhite) || []; + } + return []; + } + + jQuery.fn.extend({ + addClass: function (value) { + var classes, + elem, + cur, + curValue, + clazz, + j, + finalValue, + i = 0; + + if (isFunction(value)) { + return this.each(function (j) { + jQuery(this).addClass(value.call(this, j, getClass(this))); + }); + } + + classes = classesToArray(value); + + if (classes.length) { + while (elem = this[i++]) { + curValue = getClass(elem); + cur = elem.nodeType === 1 && " " + stripAndCollapse(curValue) + " "; + + if (cur) { + j = 0; + while (clazz = classes[j++]) { + if (cur.indexOf(" " + clazz + " ") < 0) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) { + elem.setAttribute("class", finalValue); + } + } + } + } + + return this; + }, + + removeClass: function (value) { + var classes, + elem, + cur, + curValue, + clazz, + j, + finalValue, + i = 0; + + if (isFunction(value)) { + return this.each(function (j) { + jQuery(this).removeClass(value.call(this, j, getClass(this))); + }); + } + + if (!arguments.length) { + return this.attr("class", ""); + } + + classes = classesToArray(value); + + if (classes.length) { + while (elem = this[i++]) { + curValue = getClass(elem); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && " " + stripAndCollapse(curValue) + " "; + + if (cur) { + j = 0; + while (clazz = classes[j++]) { + + // Remove *all* instances + while (cur.indexOf(" " + clazz + " ") > -1) { + cur = cur.replace(" " + clazz + " ", " "); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse(cur); + if (curValue !== finalValue) { + elem.setAttribute("class", finalValue); + } + } + } + } + + return this; + }, + + toggleClass: function (value, stateVal) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray(value); + + if (typeof stateVal === "boolean" && isValidValue) { + return stateVal ? this.addClass(value) : this.removeClass(value); + } + + if (isFunction(value)) { + return this.each(function (i) { + jQuery(this).toggleClass(value.call(this, i, getClass(this), stateVal), stateVal); + }); + } + + return this.each(function () { + var className, i, self, classNames; + + if (isValidValue) { + + // Toggle individual class names + i = 0; + self = jQuery(this); + classNames = classesToArray(value); + + while (className = classNames[i++]) { + + // Check each className given, space separated list + if (self.hasClass(className)) { + self.removeClass(className); + } else { + self.addClass(className); + } + } + + // Toggle whole class name + } else if (value === undefined || type === "boolean") { + className = getClass(this); + if (className) { + + // Store className if set + dataPriv.set(this, "__className__", className); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if (this.setAttribute) { + this.setAttribute("class", className || value === false ? "" : dataPriv.get(this, "__className__") || ""); + } + } + }); + }, + + hasClass: function (selector) { + var className, + elem, + i = 0; + + className = " " + selector + " "; + while (elem = this[i++]) { + if (elem.nodeType === 1 && (" " + stripAndCollapse(getClass(elem)) + " ").indexOf(className) > -1) { + return true; + } + } + + return false; + } + }); + + var rreturn = /\r/g; + + jQuery.fn.extend({ + val: function (value) { + var hooks, + ret, + valueIsFunction, + elem = this[0]; + + if (!arguments.length) { + if (elem) { + hooks = jQuery.valHooks[elem.type] || jQuery.valHooks[elem.nodeName.toLowerCase()]; + + if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if (typeof ret === "string") { + return ret.replace(rreturn, ""); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction(value); + + return this.each(function (i) { + var val; + + if (this.nodeType !== 1) { + return; + } + + if (valueIsFunction) { + val = value.call(this, i, jQuery(this).val()); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if (val == null) { + val = ""; + } else if (typeof val === "number") { + val += ""; + } else if (Array.isArray(val)) { + val = jQuery.map(val, function (value) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[this.type] || jQuery.valHooks[this.nodeName.toLowerCase()]; + + // If set returns undefined, fall back to normal setting + if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { + this.value = val; + } + }); + } + }); + + jQuery.extend({ + valHooks: { + option: { + get: function (elem) { + + var val = jQuery.find.attr(elem, "value"); + return val != null ? val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse(jQuery.text(elem)); + } + }, + select: { + get: function (elem) { + var value, + option, + i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if (index < 0) { + i = max; + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for (; i < max; i++) { + option = options[i]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ((option.selected || i === index) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && (!option.parentNode.disabled || !nodeName(option.parentNode, "optgroup"))) { + + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if (one) { + return value; + } + + // Multi-Selects return an array + values.push(value); + } + } + + return values; + }, + + set: function (elem, value) { + var optionSet, + option, + options = elem.options, + values = jQuery.makeArray(value), + i = options.length; + + while (i--) { + option = options[i]; + + /* eslint-disable no-cond-assign */ + + if (option.selected = jQuery.inArray(jQuery.valHooks.option.get(option), values) > -1) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if (!optionSet) { + elem.selectedIndex = -1; + } + return values; + } + } + } + }); + + // Radios and checkboxes getter/setter + jQuery.each(["radio", "checkbox"], function () { + jQuery.valHooks[this] = { + set: function (elem, value) { + if (Array.isArray(value)) { + return elem.checked = jQuery.inArray(jQuery(elem).val(), value) > -1; + } + } + }; + if (!support.checkOn) { + jQuery.valHooks[this].get = function (elem) { + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } + }); + + // Return jQuery for attributes-only inclusion + + + support.focusin = "onfocusin" in window; + + var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function (e) { + e.stopPropagation(); + }; + + jQuery.extend(jQuery.event, { + + trigger: function (event, data, elem, onlyHandlers) { + + var i, + cur, + tmp, + bubbleType, + ontype, + handle, + special, + lastElement, + eventPath = [elem || document], + type = hasOwn.call(event, "type") ? event.type : event, + namespaces = hasOwn.call(event, "namespace") ? event.namespace.split(".") : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if (rfocusMorph.test(type + jQuery.event.triggered)) { + return; + } + + if (type.indexOf(".") > -1) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[jQuery.expando] ? event : new jQuery.Event(type, typeof event === "object" && event); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.rnamespace = event.namespace ? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + + // Clean up the event in case it is being reused + event.result = undefined; + if (!event.target) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? [event] : jQuery.makeArray(data, [event]); + + // Allow special events to draw outside the lines + special = jQuery.event.special[type] || {}; + if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if (!onlyHandlers && !special.noBubble && !isWindow(elem)) { + + bubbleType = special.delegateType || type; + if (!rfocusMorph.test(bubbleType + type)) { + cur = cur.parentNode; + } + for (; cur; cur = cur.parentNode) { + eventPath.push(cur); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if (tmp === (elem.ownerDocument || document)) { + eventPath.push(tmp.defaultView || tmp.parentWindow || window); + } + } + + // Fire handlers on the event path + i = 0; + while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { + lastElement = cur; + event.type = i > 1 ? bubbleType : special.bindType || type; + + // jQuery handler + handle = (dataPriv.get(cur, "events") || {})[event.type] && dataPriv.get(cur, "handle"); + if (handle) { + handle.apply(cur, data); + } + + // Native handler + handle = ontype && cur[ontype]; + if (handle && handle.apply && acceptData(cur)) { + event.result = handle.apply(cur, data); + if (event.result === false) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if (!onlyHandlers && !event.isDefaultPrevented()) { + + if ((!special._default || special._default.apply(eventPath.pop(), data) === false) && acceptData(elem)) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if (ontype && isFunction(elem[type]) && !isWindow(elem)) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ontype]; + + if (tmp) { + elem[ontype] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if (event.isPropagationStopped()) { + lastElement.addEventListener(type, stopPropagationCallback); + } + + elem[type](); + + if (event.isPropagationStopped()) { + lastElement.removeEventListener(type, stopPropagationCallback); + } + + jQuery.event.triggered = undefined; + + if (tmp) { + elem[ontype] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function (type, elem, event) { + var e = jQuery.extend(new jQuery.Event(), event, { + type: type, + isSimulated: true + }); + + jQuery.event.trigger(e, null, elem); + } + + }); + + jQuery.fn.extend({ + + trigger: function (type, data) { + return this.each(function () { + jQuery.event.trigger(type, data, this); + }); + }, + triggerHandler: function (type, data) { + var elem = this[0]; + if (elem) { + return jQuery.event.trigger(type, data, elem, true); + } + } + }); + + // Support: Firefox <=44 + // Firefox doesn't have focus(in | out) events + // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 + // + // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 + // focus(in | out) events fire after focus & blur events, + // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order + // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + if (!support.focusin) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function (orig, fix) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function (event) { + jQuery.event.simulate(fix, event.target, jQuery.event.fix(event)); + }; + + jQuery.event.special[fix] = { + setup: function () { + var doc = this.ownerDocument || this, + attaches = dataPriv.access(doc, fix); + + if (!attaches) { + doc.addEventListener(orig, handler, true); + } + dataPriv.access(doc, fix, (attaches || 0) + 1); + }, + teardown: function () { + var doc = this.ownerDocument || this, + attaches = dataPriv.access(doc, fix) - 1; + + if (!attaches) { + doc.removeEventListener(orig, handler, true); + dataPriv.remove(doc, fix); + } else { + dataPriv.access(doc, fix, attaches); + } + } + }; + }); + } + var location = window.location; + + var nonce = Date.now(); + + var rquery = /\?/; + + // Cross-browser xml parsing + jQuery.parseXML = function (data) { + var xml; + if (!data || typeof data !== "string") { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = new window.DOMParser().parseFromString(data, "text/xml"); + } catch (e) { + xml = undefined; + } + + if (!xml || xml.getElementsByTagName("parsererror").length) { + jQuery.error("Invalid XML: " + data); + } + return xml; + }; + + var rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + + function buildParams(prefix, obj, traditional, add) { + var name; + + if (Array.isArray(obj)) { + + // Serialize array item. + jQuery.each(obj, function (i, v) { + if (traditional || rbracket.test(prefix)) { + + // Treat each array item as a scalar. + add(prefix, v); + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams(prefix + "[" + (typeof v === "object" && v != null ? i : "") + "]", v, traditional, add); + } + }); + } else if (!traditional && toType(obj) === "object") { + + // Serialize object item. + for (name in obj) { + buildParams(prefix + "[" + name + "]", obj[name], traditional, add); + } + } else { + + // Serialize scalar item. + add(prefix, obj); + } + } + + // Serialize an array of form elements or a set of + // key/values into a query string + jQuery.param = function (a, traditional) { + var prefix, + s = [], + add = function (key, valueOrFunction) { + + // If value is a function, invoke it and use its return value + var value = isFunction(valueOrFunction) ? valueOrFunction() : valueOrFunction; + + s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value == null ? "" : value); + }; + + // If an array was passed in, assume that it is an array of form elements. + if (Array.isArray(a) || a.jquery && !jQuery.isPlainObject(a)) { + + // Serialize the form elements + jQuery.each(a, function () { + add(this.name, this.value); + }); + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for (prefix in a) { + buildParams(prefix, a[prefix], traditional, add); + } + } + + // Return the resulting serialization + return s.join("&"); + }; + + jQuery.fn.extend({ + serialize: function () { + return jQuery.param(this.serializeArray()); + }, + serializeArray: function () { + return this.map(function () { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop(this, "elements"); + return elements ? jQuery.makeArray(elements) : this; + }).filter(function () { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery(this).is(":disabled") && rsubmittable.test(this.nodeName) && !rsubmitterTypes.test(type) && (this.checked || !rcheckableType.test(type)); + }).map(function (i, elem) { + var val = jQuery(this).val(); + + if (val == null) { + return null; + } + + if (Array.isArray(val)) { + return jQuery.map(val, function (val) { + return { name: elem.name, value: val.replace(rCRLF, "\r\n") }; + }); + } + + return { name: elem.name, value: val.replace(rCRLF, "\r\n") }; + }).get(); + } + }); + + var r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat("*"), + + + // Anchor tag for parsing the document origin + originAnchor = document.createElement("a"); + originAnchor.href = location.href; + + // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport + function addToPrefiltersOrTransports(structure) { + + // dataTypeExpression is optional and defaults to "*" + return function (dataTypeExpression, func) { + + if (typeof dataTypeExpression !== "string") { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match(rnothtmlwhite) || []; + + if (isFunction(func)) { + + // For each dataType in the dataTypeExpression + while (dataType = dataTypes[i++]) { + + // Prepend if requested + if (dataType[0] === "+") { + dataType = dataType.slice(1) || "*"; + (structure[dataType] = structure[dataType] || []).unshift(func); + + // Otherwise append + } else { + (structure[dataType] = structure[dataType] || []).push(func); + } + } + } + }; + } + + // Base inspection function for prefilters and transports + function inspectPrefiltersOrTransports(structure, options, originalOptions, jqXHR) { + + var inspected = {}, + seekingTransport = structure === transports; + + function inspect(dataType) { + var selected; + inspected[dataType] = true; + jQuery.each(structure[dataType] || [], function (_, prefilterOrFactory) { + var dataTypeOrTransport = prefilterOrFactory(options, originalOptions, jqXHR); + if (typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[dataTypeOrTransport]) { + + options.dataTypes.unshift(dataTypeOrTransport); + inspect(dataTypeOrTransport); + return false; + } else if (seekingTransport) { + return !(selected = dataTypeOrTransport); + } + }); + return selected; + } + + return inspect(options.dataTypes[0]) || !inspected["*"] && inspect("*"); + } + + // A special extend for ajax options + // that takes "flat" options (not to be deep extended) + // Fixes #9887 + function ajaxExtend(target, src) { + var key, + deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for (key in src) { + if (src[key] !== undefined) { + (flatOptions[key] ? target : deep || (deep = {}))[key] = src[key]; + } + } + if (deep) { + jQuery.extend(true, target, deep); + } + + return target; + } + + /* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ + function ajaxHandleResponses(s, jqXHR, responses) { + + var ct, + type, + finalDataType, + firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while (dataTypes[0] === "*") { + dataTypes.shift(); + if (ct === undefined) { + ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); + } + } + + // Check if we're dealing with a known content-type + if (ct) { + for (type in contents) { + if (contents[type] && contents[type].test(ct)) { + dataTypes.unshift(type); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if (dataTypes[0] in responses) { + finalDataType = dataTypes[0]; + } else { + + // Try convertible dataTypes + for (type in responses) { + if (!dataTypes[0] || s.converters[type + " " + dataTypes[0]]) { + finalDataType = type; + break; + } + if (!firstDataType) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if (finalDataType) { + if (finalDataType !== dataTypes[0]) { + dataTypes.unshift(finalDataType); + } + return responses[finalDataType]; + } + } + + /* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ + function ajaxConvert(s, response, jqXHR, isSuccess) { + var conv2, + current, + conv, + tmp, + prev, + converters = {}, + + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if (dataTypes[1]) { + for (conv in s.converters) { + converters[conv.toLowerCase()] = s.converters[conv]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while (current) { + + if (s.responseFields[current]) { + jqXHR[s.responseFields[current]] = response; + } + + // Apply the dataFilter if provided + if (!prev && isSuccess && s.dataFilter) { + response = s.dataFilter(response, s.dataType); + } + + prev = current; + current = dataTypes.shift(); + + if (current) { + + // There's only work to do if current dataType is non-auto + if (current === "*") { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if (prev !== "*" && prev !== current) { + + // Seek a direct converter + conv = converters[prev + " " + current] || converters["* " + current]; + + // If none found, seek a pair + if (!conv) { + for (conv2 in converters) { + + // If conv2 outputs current + tmp = conv2.split(" "); + if (tmp[1] === current) { + + // If prev can be converted to accepted input + conv = converters[prev + " " + tmp[0]] || converters["* " + tmp[0]]; + if (conv) { + + // Condense equivalence converters + if (conv === true) { + conv = converters[conv2]; + + // Otherwise, insert the intermediate dataType + } else if (converters[conv2] !== true) { + current = tmp[0]; + dataTypes.unshift(tmp[1]); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if (conv !== true) { + + // Unless errors are allowed to bubble, catch and return them + if (conv && s.throws) { + response = conv(response); + } else { + try { + response = conv(response); + } catch (e) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; + } + + jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test(location.protocol), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function (target, settings) { + return settings ? + + // Building a settings object + ajaxExtend(ajaxExtend(target, jQuery.ajaxSettings), settings) : + + // Extending ajaxSettings + ajaxExtend(jQuery.ajaxSettings, target); + }, + + ajaxPrefilter: addToPrefiltersOrTransports(prefilters), + ajaxTransport: addToPrefiltersOrTransports(transports), + + // Main method + ajax: function (url, options) { + + // If url is an object, simulate pre-1.5 signature + if (typeof url === "object") { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + + // URL without anti-cache param + cacheURL, + + + // Response headers + responseHeadersString, + responseHeaders, + + + // timeout handle + timeoutTimer, + + + // Url cleanup var + urlAnchor, + + + // Request state (becomes false upon send and true upon completion) + completed, + + + // To know if global events are to be dispatched + fireGlobals, + + + // Loop variable + i, + + + // uncached part of the url + uncached, + + + // Create the final options object + s = jQuery.ajaxSetup({}, options), + + + // Callbacks context + callbackContext = s.context || s, + + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && (callbackContext.nodeType || callbackContext.jquery) ? jQuery(callbackContext) : jQuery.event, + + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks("once memory"), + + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + + // Default abort message + strAbort = "canceled", + + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function (key) { + var match; + if (completed) { + if (!responseHeaders) { + responseHeaders = {}; + while (match = rheaders.exec(responseHeadersString)) { + responseHeaders[match[1].toLowerCase()] = match[2]; + } + } + match = responseHeaders[key.toLowerCase()]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function () { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function (name, value) { + if (completed == null) { + name = requestHeadersNames[name.toLowerCase()] = requestHeadersNames[name.toLowerCase()] || name; + requestHeaders[name] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function (type) { + if (completed == null) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function (map) { + var code; + if (map) { + if (completed) { + + // Execute the appropriate callbacks + jqXHR.always(map[jqXHR.status]); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for (code in map) { + statusCode[code] = [statusCode[code], map[code]]; + } + } + } + return this; + }, + + // Cancel the request + abort: function (statusText) { + var finalText = statusText || strAbort; + if (transport) { + transport.abort(finalText); + } + done(0, finalText); + return this; + } + }; + + // Attach deferreds + deferred.promise(jqXHR); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ((url || s.url || location.href) + "").replace(rprotocol, location.protocol + "//"); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = (s.dataType || "*").toLowerCase().match(rnothtmlwhite) || [""]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if (s.crossDomain == null) { + urlAnchor = document.createElement("a"); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== urlAnchor.protocol + "//" + urlAnchor.host; + } catch (e) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if (s.data && s.processData && typeof s.data !== "string") { + s.data = jQuery.param(s.data, s.traditional); + } + + // Apply prefilters + inspectPrefiltersOrTransports(prefilters, s, options, jqXHR); + + // If request was aborted inside a prefilter, stop there + if (completed) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if (fireGlobals && jQuery.active++ === 0) { + jQuery.event.trigger("ajaxStart"); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test(s.type); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace(rhash, ""); + + // More options handling for requests with no content + if (!s.hasContent) { + + // Remember the hash so we can put it back + uncached = s.url.slice(cacheURL.length); + + // If data is available and should be processed, append data to url + if (s.data && (s.processData || typeof s.data === "string")) { + cacheURL += (rquery.test(cacheURL) ? "&" : "?") + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if (s.cache === false) { + cacheURL = cacheURL.replace(rantiCache, "$1"); + uncached = (rquery.test(cacheURL) ? "&" : "?") + "_=" + nonce++ + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if (s.data && s.processData && (s.contentType || "").indexOf("application/x-www-form-urlencoded") === 0) { + s.data = s.data.replace(r20, "+"); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if (s.ifModified) { + if (jQuery.lastModified[cacheURL]) { + jqXHR.setRequestHeader("If-Modified-Since", jQuery.lastModified[cacheURL]); + } + if (jQuery.etag[cacheURL]) { + jqXHR.setRequestHeader("If-None-Match", jQuery.etag[cacheURL]); + } + } + + // Set the correct header, if data is being sent + if (s.data && s.hasContent && s.contentType !== false || options.contentType) { + jqXHR.setRequestHeader("Content-Type", s.contentType); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader("Accept", s.dataTypes[0] && s.accepts[s.dataTypes[0]] ? s.accepts[s.dataTypes[0]] + (s.dataTypes[0] !== "*" ? ", " + allTypes + "; q=0.01" : "") : s.accepts["*"]); + + // Check for headers option + for (i in s.headers) { + jqXHR.setRequestHeader(i, s.headers[i]); + } + + // Allow custom headers/mimetypes and early abort + if (s.beforeSend && (s.beforeSend.call(callbackContext, jqXHR, s) === false || completed)) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add(s.complete); + jqXHR.done(s.success); + jqXHR.fail(s.error); + + // Get transport + transport = inspectPrefiltersOrTransports(transports, s, options, jqXHR); + + // If no transport, we auto-abort + if (!transport) { + done(-1, "No Transport"); + } else { + jqXHR.readyState = 1; + + // Send global event + if (fireGlobals) { + globalEventContext.trigger("ajaxSend", [jqXHR, s]); + } + + // If request was aborted inside ajaxSend, stop there + if (completed) { + return jqXHR; + } + + // Timeout + if (s.async && s.timeout > 0) { + timeoutTimer = window.setTimeout(function () { + jqXHR.abort("timeout"); + }, s.timeout); + } + + try { + completed = false; + transport.send(requestHeaders, done); + } catch (e) { + + // Rethrow post-completion exceptions + if (completed) { + throw e; + } + + // Propagate others as results + done(-1, e); + } + } + + // Callback for when everything is done + function done(status, nativeStatusText, responses, headers) { + var isSuccess, + success, + error, + response, + modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if (completed) { + return; + } + + completed = true; + + // Clear timeout if it exists + if (timeoutTimer) { + window.clearTimeout(timeoutTimer); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if (responses) { + response = ajaxHandleResponses(s, jqXHR, responses); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert(s, response, jqXHR, isSuccess); + + // If successful, handle type chaining + if (isSuccess) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if (s.ifModified) { + modified = jqXHR.getResponseHeader("Last-Modified"); + if (modified) { + jQuery.lastModified[cacheURL] = modified; + } + modified = jqXHR.getResponseHeader("etag"); + if (modified) { + jQuery.etag[cacheURL] = modified; + } + } + + // if no content + if (status === 204 || s.type === "HEAD") { + statusText = "nocontent"; + + // if not modified + } else if (status === 304) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if (status || !statusText) { + statusText = "error"; + if (status < 0) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = (nativeStatusText || statusText) + ""; + + // Success/Error + if (isSuccess) { + deferred.resolveWith(callbackContext, [success, statusText, jqXHR]); + } else { + deferred.rejectWith(callbackContext, [jqXHR, statusText, error]); + } + + // Status-dependent callbacks + jqXHR.statusCode(statusCode); + statusCode = undefined; + + if (fireGlobals) { + globalEventContext.trigger(isSuccess ? "ajaxSuccess" : "ajaxError", [jqXHR, s, isSuccess ? success : error]); + } + + // Complete + completeDeferred.fireWith(callbackContext, [jqXHR, statusText]); + + if (fireGlobals) { + globalEventContext.trigger("ajaxComplete", [jqXHR, s]); + + // Handle the global AJAX counter + if (! --jQuery.active) { + jQuery.event.trigger("ajaxStop"); + } + } + } + + return jqXHR; + }, + + getJSON: function (url, data, callback) { + return jQuery.get(url, data, callback, "json"); + }, + + getScript: function (url, callback) { + return jQuery.get(url, undefined, callback, "script"); + } + }); + + jQuery.each(["get", "post"], function (i, method) { + jQuery[method] = function (url, data, callback, type) { + + // Shift arguments if data argument was omitted + if (isFunction(data)) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax(jQuery.extend({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject(url) && url)); + }; + }); + + jQuery._evalUrl = function (url) { + return jQuery.ajax({ + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + }); + }; + + jQuery.fn.extend({ + wrapAll: function (html) { + var wrap; + + if (this[0]) { + if (isFunction(html)) { + html = html.call(this[0]); + } + + // The elements to wrap the target around + wrap = jQuery(html, this[0].ownerDocument).eq(0).clone(true); + + if (this[0].parentNode) { + wrap.insertBefore(this[0]); + } + + wrap.map(function () { + var elem = this; + + while (elem.firstElementChild) { + elem = elem.firstElementChild; + } + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function (html) { + if (isFunction(html)) { + return this.each(function (i) { + jQuery(this).wrapInner(html.call(this, i)); + }); + } + + return this.each(function () { + var self = jQuery(this), + contents = self.contents(); + + if (contents.length) { + contents.wrapAll(html); + } else { + self.append(html); + } + }); + }, + + wrap: function (html) { + var htmlIsFunction = isFunction(html); + + return this.each(function (i) { + jQuery(this).wrapAll(htmlIsFunction ? html.call(this, i) : html); + }); + }, + + unwrap: function (selector) { + this.parent(selector).not("body").each(function () { + jQuery(this).replaceWith(this.childNodes); + }); + return this; + } + }); + + jQuery.expr.pseudos.hidden = function (elem) { + return !jQuery.expr.pseudos.visible(elem); + }; + jQuery.expr.pseudos.visible = function (elem) { + return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); + }; + + jQuery.ajaxSettings.xhr = function () { + try { + return new window.XMLHttpRequest(); + } catch (e) {} + }; + + var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + + support.cors = !!xhrSupported && "withCredentials" in xhrSupported; + support.ajax = xhrSupported = !!xhrSupported; + + jQuery.ajaxTransport(function (options) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if (support.cors || xhrSupported && !options.crossDomain) { + return { + send: function (headers, complete) { + var i, + xhr = options.xhr(); + + xhr.open(options.type, options.url, options.async, options.username, options.password); + + // Apply custom fields if provided + if (options.xhrFields) { + for (i in options.xhrFields) { + xhr[i] = options.xhrFields[i]; + } + } + + // Override mime type if needed + if (options.mimeType && xhr.overrideMimeType) { + xhr.overrideMimeType(options.mimeType); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if (!options.crossDomain && !headers["X-Requested-With"]) { + headers["X-Requested-With"] = "XMLHttpRequest"; + } + + // Set headers + for (i in headers) { + xhr.setRequestHeader(i, headers[i]); + } + + // Callback + callback = function (type) { + return function () { + if (callback) { + callback = errorCallback = xhr.onload = xhr.onerror = xhr.onabort = xhr.ontimeout = xhr.onreadystatechange = null; + + if (type === "abort") { + xhr.abort(); + } else if (type === "error") { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if (typeof xhr.status !== "number") { + complete(0, "error"); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, xhr.statusText); + } + } else { + complete(xhrSuccessStatus[xhr.status] || xhr.status, xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + (xhr.responseType || "text") !== "text" || typeof xhr.responseText !== "string" ? { binary: xhr.response } : { text: xhr.responseText }, xhr.getAllResponseHeaders()); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback("error"); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if (xhr.onabort !== undefined) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function () { + + // Check readyState before timeout as it changes + if (xhr.readyState === 4) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout(function () { + if (callback) { + errorCallback(); + } + }); + } + }; + } + + // Create the abort callback + callback = callback("abort"); + + try { + + // Do send the request (this may raise an exception) + xhr.send(options.hasContent && options.data || null); + } catch (e) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if (callback) { + throw e; + } + } + }, + + abort: function () { + if (callback) { + callback(); + } + } + }; + } + }); + + // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) + jQuery.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); + + // Install script dataType + jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function (text) { + jQuery.globalEval(text); + return text; + } + } + }); + + // Handle cache's special case and crossDomain + jQuery.ajaxPrefilter("script", function (s) { + if (s.cache === undefined) { + s.cache = false; + } + if (s.crossDomain) { + s.type = "GET"; + } + }); + + // Bind script tag hack transport + jQuery.ajaxTransport("script", function (s) { + + // This transport only deals with cross domain requests + if (s.crossDomain) { + var script, callback; + return { + send: function (_, complete) { + script = jQuery("