2011-11-18 10:28:17 -08:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
2012-03-13 08:15:43 -07:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.graphics.drawable.Drawable;
|
2012-06-05 13:29:37 -07:00
|
|
|
import android.graphics.PointF;
|
2012-03-19 12:36:18 -07:00
|
|
|
import android.text.TextUtils;
|
2012-01-27 12:18:02 -08:00
|
|
|
import android.util.AttributeSet;
|
2012-06-05 13:29:37 -07:00
|
|
|
import android.view.GestureDetector;
|
|
|
|
import android.view.GestureDetector.SimpleOnGestureListener;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.view.LayoutInflater;
|
2012-06-05 13:29:37 -07:00
|
|
|
import android.view.MotionEvent;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.view.View;
|
|
|
|
import android.view.ViewGroup;
|
2012-04-30 14:09:41 -07:00
|
|
|
import android.widget.AbsListView.RecyclerListener;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.widget.BaseAdapter;
|
|
|
|
import android.widget.Button;
|
|
|
|
import android.widget.ImageButton;
|
|
|
|
import android.widget.ImageView;
|
|
|
|
import android.widget.LinearLayout;
|
|
|
|
import android.widget.ListView;
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
2012-06-05 13:29:37 -07:00
|
|
|
import org.mozilla.gecko.gfx.PointUtils;
|
|
|
|
import org.mozilla.gecko.PropertyAnimator.Property;
|
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
public class TabsTray extends LinearLayout
|
|
|
|
implements TabsPanel.PanelView,
|
|
|
|
Tabs.OnTabsChangedListener {
|
|
|
|
private static final String LOGTAG = "GeckoTabsTray";
|
2012-03-13 08:15:43 -07:00
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
private Context mContext;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-01-27 12:18:02 -08:00
|
|
|
private static ListView mList;
|
2011-11-18 10:28:17 -08:00
|
|
|
private TabsAdapter mTabsAdapter;
|
2011-11-21 12:21:10 -08:00
|
|
|
private boolean mWaitingForClose;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-05 13:29:37 -07:00
|
|
|
private GestureDetector mGestureDetector;
|
|
|
|
private TabSwipeGestureListener mListener;
|
|
|
|
private static final int SWIPE_CLOSE_VELOCITY = 1000;
|
|
|
|
private static final int MAX_ANIMATION_TIME = 250;
|
|
|
|
private static enum DragDirection {
|
|
|
|
UNKNOWN,
|
|
|
|
HORIZONTAL,
|
|
|
|
VERTICAL
|
|
|
|
}
|
|
|
|
|
2012-03-19 12:36:18 -07:00
|
|
|
private static final String ABOUT_HOME = "about:home";
|
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
public TabsTray(Context context, AttributeSet attrs) {
|
|
|
|
super(context, attrs);
|
|
|
|
mContext = context;
|
2012-03-23 12:00:17 -07:00
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
LayoutInflater.from(context).inflate(R.layout.tabs_tray, this);
|
2011-11-21 12:21:10 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
mList = (ListView) findViewById(R.id.list);
|
2012-04-25 17:09:12 -07:00
|
|
|
mList.setItemsCanFocus(true);
|
2012-06-05 13:29:37 -07:00
|
|
|
mListener = new TabSwipeGestureListener(mList);
|
|
|
|
mGestureDetector = new GestureDetector(context, mListener);
|
|
|
|
|
|
|
|
mList.setOnTouchListener(new View.OnTouchListener() {
|
|
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
|
|
boolean result = mGestureDetector.onTouchEvent(event);
|
|
|
|
|
|
|
|
// if this is an touch end event, we need to reset the state
|
|
|
|
// of the gesture listener
|
|
|
|
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
|
|
mListener.onTouchEnd(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
});
|
2012-04-25 17:09:12 -07:00
|
|
|
|
2012-04-30 14:09:41 -07:00
|
|
|
mList.setRecyclerListener(new RecyclerListener() {
|
|
|
|
@Override
|
|
|
|
public void onMovedToScrapHeap(View view) {
|
2012-05-01 10:50:27 -07:00
|
|
|
TabRow row = (TabRow) view.getTag();
|
|
|
|
row.thumbnail.setImageDrawable(null);
|
2012-04-30 14:09:41 -07:00
|
|
|
}
|
|
|
|
});
|
2012-06-07 21:47:22 -07:00
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
@Override
|
|
|
|
public ViewGroup getLayout() {
|
|
|
|
return this;
|
|
|
|
}
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
@Override
|
|
|
|
public void show() {
|
|
|
|
mWaitingForClose = false;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-03-15 12:01:52 -07:00
|
|
|
Tabs.registerOnTabsChangedListener(this);
|
|
|
|
Tabs.getInstance().refreshThumbnails();
|
2012-06-11 15:18:40 -07:00
|
|
|
onTabChanged(null, null, null);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2012-06-07 21:47:22 -07:00
|
|
|
public void hide() {
|
2012-03-15 12:01:52 -07:00
|
|
|
Tabs.unregisterOnTabsChangedListener(this);
|
2012-06-07 21:47:22 -07:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Screenshot:Cancel",""));
|
|
|
|
mTabsAdapter.clear();
|
|
|
|
mTabsAdapter.notifyDataSetChanged();
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2012-06-11 15:18:40 -07:00
|
|
|
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
2011-11-18 10:28:17 -08:00
|
|
|
if (mTabsAdapter == null) {
|
2012-06-07 21:47:22 -07:00
|
|
|
mTabsAdapter = new TabsAdapter(mContext, Tabs.getInstance().getTabsInOrder());
|
2011-11-18 10:28:17 -08:00
|
|
|
mList.setAdapter(mTabsAdapter);
|
2012-02-14 16:27:51 -08:00
|
|
|
|
|
|
|
int selected = mTabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
|
|
|
|
if (selected == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mList.setSelection(selected);
|
2011-11-18 10:28:17 -08:00
|
|
|
return;
|
|
|
|
}
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
int position = mTabsAdapter.getPositionForTab(tab);
|
|
|
|
if (position == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (Tabs.getInstance().getIndexOf(tab) == -1) {
|
2011-11-21 12:21:10 -08:00
|
|
|
mWaitingForClose = false;
|
2012-02-13 16:50:09 -08:00
|
|
|
mTabsAdapter.removeTab(tab);
|
2012-05-16 15:40:16 -07:00
|
|
|
mTabsAdapter.notifyDataSetChanged();
|
2011-11-18 10:28:17 -08:00
|
|
|
} else {
|
|
|
|
View view = mList.getChildAt(position - mList.getFirstVisiblePosition());
|
2012-05-01 10:50:27 -07:00
|
|
|
if (view == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TabRow row = (TabRow) view.getTag();
|
|
|
|
mTabsAdapter.assignValues(row, tab);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
void hideTabs() {
|
|
|
|
GeckoApp.mAppContext.hideTabs();
|
2012-03-09 10:42:43 -08:00
|
|
|
}
|
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
// ViewHolder for a row in the list
|
|
|
|
private class TabRow {
|
|
|
|
int id;
|
|
|
|
TextView title;
|
|
|
|
ImageView thumbnail;
|
|
|
|
ImageButton close;
|
2012-06-07 21:47:22 -07:00
|
|
|
LinearLayout info;
|
2012-05-01 10:50:27 -07:00
|
|
|
|
|
|
|
public TabRow(View view) {
|
2012-06-07 21:47:22 -07:00
|
|
|
info = (LinearLayout) view;
|
2012-05-01 10:50:27 -07:00
|
|
|
title = (TextView) view.findViewById(R.id.title);
|
|
|
|
thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
|
|
|
|
close = (ImageButton) view.findViewById(R.id.close);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-09 10:42:43 -08:00
|
|
|
// Adapter to bind tabs into a list
|
2011-11-18 10:28:17 -08:00
|
|
|
private class TabsAdapter extends BaseAdapter {
|
2012-05-01 10:50:27 -07:00
|
|
|
private Context mContext;
|
|
|
|
private ArrayList<Tab> mTabs;
|
|
|
|
private LayoutInflater mInflater;
|
|
|
|
private Button.OnClickListener mOnCloseClickListener;
|
|
|
|
|
2011-11-22 12:32:45 -08:00
|
|
|
public TabsAdapter(Context context, ArrayList<Tab> tabs) {
|
2011-11-18 10:28:17 -08:00
|
|
|
mContext = context;
|
2011-11-21 12:21:10 -08:00
|
|
|
mInflater = LayoutInflater.from(mContext);
|
2011-11-18 10:28:17 -08:00
|
|
|
mTabs = new ArrayList<Tab>();
|
2011-11-21 12:21:10 -08:00
|
|
|
|
|
|
|
if (tabs == null)
|
|
|
|
return;
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
for (int i = 0; i < tabs.size(); i++) {
|
|
|
|
mTabs.add(tabs.get(i));
|
|
|
|
}
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2011-11-22 12:32:45 -08:00
|
|
|
mOnCloseClickListener = new Button.OnClickListener() {
|
|
|
|
public void onClick(View v) {
|
2012-06-05 13:29:37 -07:00
|
|
|
TabRow tab = (TabRow) v.getTag();
|
|
|
|
animateTo(tab.info, tab.info.getWidth(), MAX_ANIMATION_TIME);
|
2011-11-22 12:32:45 -08:00
|
|
|
}
|
|
|
|
};
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-06-07 21:47:22 -07:00
|
|
|
public void clear() {
|
|
|
|
mTabs = null;
|
|
|
|
}
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
public int getCount() {
|
2012-06-07 21:47:22 -07:00
|
|
|
return (mTabs == null ? 0 : mTabs.size());
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
public Tab getItem(int position) {
|
|
|
|
return mTabs.get(position);
|
|
|
|
}
|
|
|
|
|
|
|
|
public long getItemId(int position) {
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getPositionForTab(Tab tab) {
|
|
|
|
if (mTabs == null || tab == null)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return mTabs.indexOf(tab);
|
|
|
|
}
|
|
|
|
|
2012-02-13 16:50:09 -08:00
|
|
|
public void removeTab(Tab tab) {
|
|
|
|
mTabs.remove(tab);
|
|
|
|
}
|
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
public void assignValues(TabRow row, Tab tab) {
|
|
|
|
if (row == null || tab == null)
|
2011-11-18 10:28:17 -08:00
|
|
|
return;
|
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
row.id = tab.getId();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2011-12-14 14:31:39 -08:00
|
|
|
Drawable thumbnailImage = tab.getThumbnail();
|
|
|
|
if (thumbnailImage != null)
|
2012-05-01 10:50:27 -07:00
|
|
|
row.thumbnail.setImageDrawable(thumbnailImage);
|
2012-03-19 12:36:18 -07:00
|
|
|
else if (TextUtils.equals(tab.getURL(), ABOUT_HOME))
|
2012-05-01 10:50:27 -07:00
|
|
|
row.thumbnail.setImageResource(R.drawable.abouthome_thumbnail);
|
2011-11-18 10:28:17 -08:00
|
|
|
else
|
2012-05-01 10:50:27 -07:00
|
|
|
row.thumbnail.setImageResource(R.drawable.tab_thumbnail_default);
|
2011-12-14 14:31:39 -08:00
|
|
|
|
|
|
|
if (Tabs.getInstance().isSelectedTab(tab))
|
2012-06-07 21:47:22 -07:00
|
|
|
row.info.setBackgroundResource(R.drawable.tabs_tray_active_selector);
|
2012-05-01 10:50:27 -07:00
|
|
|
else
|
2012-06-07 21:47:22 -07:00
|
|
|
row.info.setBackgroundResource(R.drawable.tabs_tray_default_selector);
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-06-05 13:29:37 -07:00
|
|
|
// this may be a recycled view that was animated off screen
|
|
|
|
// reset the scroll state here
|
|
|
|
row.info.scrollTo(0,0);
|
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
row.title.setText(tab.getDisplayTitle());
|
|
|
|
|
2012-06-05 13:29:37 -07:00
|
|
|
row.close.setTag(row);
|
2012-06-07 21:47:22 -07:00
|
|
|
row.close.setVisibility(mTabs.size() > 1 ? View.VISIBLE : View.INVISIBLE);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public View getView(int position, View convertView, ViewGroup parent) {
|
2012-05-01 10:50:27 -07:00
|
|
|
TabRow row;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
if (convertView == null) {
|
|
|
|
convertView = mInflater.inflate(R.layout.tabs_row, null);
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
row = new TabRow(convertView);
|
|
|
|
row.close.setOnClickListener(mOnCloseClickListener);
|
2012-03-09 10:42:43 -08:00
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
convertView.setTag(row);
|
2011-11-18 10:28:17 -08:00
|
|
|
} else {
|
2012-05-01 10:50:27 -07:00
|
|
|
row = (TabRow) convertView.getTag();
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2012-05-01 10:50:27 -07:00
|
|
|
Tab tab = mTabs.get(position);
|
|
|
|
assignValues(row, tab);
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
return convertView;
|
|
|
|
}
|
|
|
|
}
|
2012-06-05 13:29:37 -07:00
|
|
|
|
|
|
|
private void animateTo(final View view, int x, int duration) {
|
|
|
|
PropertyAnimator pa = new PropertyAnimator(duration);
|
|
|
|
pa.attach(view, Property.SLIDE_LEFT, x);
|
|
|
|
if (x != 0 && !mWaitingForClose) {
|
|
|
|
mWaitingForClose = true;
|
|
|
|
|
|
|
|
TabRow tab = (TabRow)view.getTag();
|
|
|
|
final int tabId = tab.id;
|
|
|
|
|
|
|
|
pa.setPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
|
|
|
public void onPropertyAnimationStart() { }
|
|
|
|
public void onPropertyAnimationEnd() {
|
|
|
|
Tabs tabs = Tabs.getInstance();
|
|
|
|
Tab tab = tabs.getTab(tabId);
|
|
|
|
tabs.closeTab(tab);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
pa.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
private class TabSwipeGestureListener extends SimpleOnGestureListener {
|
|
|
|
private View mList = null;
|
|
|
|
private View mView = null;
|
|
|
|
private PointF start = null;
|
|
|
|
private DragDirection dir = DragDirection.UNKNOWN;
|
|
|
|
|
|
|
|
public TabSwipeGestureListener(View v) {
|
|
|
|
mList = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onDown(MotionEvent e) {
|
|
|
|
mView = findViewAt((int)e.getX(), (int)e.getY());
|
|
|
|
if (mView == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
mView.setPressed(true);
|
|
|
|
start = new PointF(e.getX(), e.getY());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean onTouchEnd(MotionEvent e) {
|
|
|
|
if (mView != null) {
|
|
|
|
|
|
|
|
// if the user was dragging horizontally, check to see if we should close the tab
|
|
|
|
if (dir == DragDirection.HORIZONTAL) {
|
|
|
|
|
|
|
|
int finalPos = 0;
|
|
|
|
if ((start.x > mList.getWidth()/2 && e.getX() < mList.getWidth()/2)) {
|
|
|
|
finalPos = -1 * mView.getWidth();
|
|
|
|
} else if (start.x < mList.getWidth()/2 && e.getX() > mList.getWidth()/2) {
|
|
|
|
finalPos = mView.getWidth();
|
|
|
|
}
|
|
|
|
|
|
|
|
animateTo(mView, finalPos, MAX_ANIMATION_TIME);
|
|
|
|
} else if (mView != null && dir == DragDirection.UNKNOWN) {
|
|
|
|
// the user didn't attempt to scroll the view, so select the row
|
|
|
|
TabRow tab = (TabRow)mView.getTag();
|
|
|
|
int tabId = tab.id;
|
|
|
|
Tabs.getInstance().selectTab(tabId);
|
|
|
|
hideTabs();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mView = null;
|
|
|
|
start = null;
|
|
|
|
dir = DragDirection.UNKNOWN;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
|
|
|
|
if (mView == null)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// if there is only one tab left, we want to recognize the scroll and
|
|
|
|
// stop any click/selection events, but not scroll/close the view
|
|
|
|
if (Tabs.getInstance().getCount() == 1) {
|
|
|
|
mView.setPressed(false);
|
|
|
|
mView = null;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dir == DragDirection.UNKNOWN) {
|
|
|
|
if (Math.abs(distanceX) > Math.abs(distanceY)) {
|
|
|
|
dir = DragDirection.HORIZONTAL;
|
|
|
|
} else {
|
|
|
|
dir = DragDirection.VERTICAL;
|
|
|
|
}
|
|
|
|
mView.setPressed(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dir == DragDirection.HORIZONTAL) {
|
|
|
|
mView.scrollBy((int) distanceX, 0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
|
|
|
|
if (mView == null || Tabs.getInstance().getCount() == 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (Math.abs(velocityX)/GeckoAppShell.getDpi() > SWIPE_CLOSE_VELOCITY) {
|
|
|
|
float d = (velocityX > 0 ? 1 : -1) * mView.getWidth();
|
|
|
|
animateTo(mView, (int)d, (int)(d/velocityX));
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private View findViewAt(int x, int y) {
|
|
|
|
if (mList == null)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
ListView list = (ListView)mList;
|
|
|
|
x += list.getScrollX();
|
|
|
|
y += list.getScrollY();
|
|
|
|
|
|
|
|
final int count = list.getChildCount();
|
|
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
|
|
View child = list.getChildAt(i);
|
|
|
|
if (child.getVisibility() == View.VISIBLE) {
|
|
|
|
if ((x >= child.getLeft()) && (x < child.getRight())
|
|
|
|
&& (y >= child.getTop()) && (y < child.getBottom())) {
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2012-06-05 13:29:37 -07:00
|
|
|
|