1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.app.Activity;
24import android.content.Context;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.graphics.BitmapShader;
29import android.graphics.Canvas;
30import android.graphics.Matrix;
31import android.graphics.Paint;
32import android.graphics.Path;
33import android.graphics.Shader;
34import android.graphics.drawable.Drawable;
35import android.view.Gravity;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.View.OnClickListener;
39import android.widget.ImageButton;
40import android.widget.ImageView;
41import android.widget.LinearLayout;
42import android.widget.TextView;
43
44import java.util.HashMap;
45import java.util.List;
46import java.util.Map;
47
48/**
49 * tabbed title bar for xlarge screen browser
50 */
51public class TabBar extends LinearLayout implements OnClickListener {
52
53    private static final int PROGRESS_MAX = 100;
54
55    private Activity mActivity;
56    private UiController mUiController;
57    private TabControl mTabControl;
58    private XLargeUi mUi;
59
60    private int mTabWidth;
61
62    private TabScrollView mTabs;
63
64    private ImageButton mNewTab;
65    private int mButtonWidth;
66
67    private Map<Tab, TabView> mTabMap;
68
69    private int mCurrentTextureWidth = 0;
70    private int mCurrentTextureHeight = 0;
71
72    private Drawable mActiveDrawable;
73    private Drawable mInactiveDrawable;
74
75    private final Paint mActiveShaderPaint = new Paint();
76    private final Paint mInactiveShaderPaint = new Paint();
77    private final Paint mFocusPaint = new Paint();
78    private final Matrix mActiveMatrix = new Matrix();
79    private final Matrix mInactiveMatrix = new Matrix();
80
81    private BitmapShader mActiveShader;
82    private BitmapShader mInactiveShader;
83
84    private int mTabOverlap;
85    private int mAddTabOverlap;
86    private int mTabSliceWidth;
87    private boolean mUseQuickControls;
88
89    public TabBar(Activity activity, UiController controller, XLargeUi ui) {
90        super(activity);
91        mActivity = activity;
92        mUiController = controller;
93        mTabControl = mUiController.getTabControl();
94        mUi = ui;
95        Resources res = activity.getResources();
96        mTabWidth = (int) res.getDimension(R.dimen.tab_width);
97        mActiveDrawable = res.getDrawable(R.drawable.bg_urlbar);
98        mInactiveDrawable = res.getDrawable(R.drawable.browsertab_inactive);
99
100        mTabMap = new HashMap<Tab, TabView>();
101        LayoutInflater factory = LayoutInflater.from(activity);
102        factory.inflate(R.layout.tab_bar, this);
103        setPadding(0, (int) res.getDimension(R.dimen.tab_padding_top), 0, 0);
104        mTabs = (TabScrollView) findViewById(R.id.tabs);
105        mNewTab = (ImageButton) findViewById(R.id.newtab);
106        mNewTab.setOnClickListener(this);
107
108        updateTabs(mUiController.getTabs());
109        mButtonWidth = -1;
110        // tab dimensions
111        mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
112        mAddTabOverlap = (int) res.getDimension(R.dimen.tab_addoverlap);
113        mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
114
115        mActiveShaderPaint.setStyle(Paint.Style.FILL);
116        mActiveShaderPaint.setAntiAlias(true);
117
118        mInactiveShaderPaint.setStyle(Paint.Style.FILL);
119        mInactiveShaderPaint.setAntiAlias(true);
120
121        mFocusPaint.setStyle(Paint.Style.STROKE);
122        mFocusPaint.setStrokeWidth(res.getDimension(R.dimen.tab_focus_stroke));
123        mFocusPaint.setAntiAlias(true);
124        mFocusPaint.setColor(res.getColor(R.color.tabFocusHighlight));
125    }
126
127    @Override
128    public void onConfigurationChanged(Configuration config) {
129        super.onConfigurationChanged(config);
130        Resources res = mActivity.getResources();
131        mTabWidth = (int) res.getDimension(R.dimen.tab_width);
132        // force update of tab bar
133        mTabs.updateLayout();
134    }
135
136    void setUseQuickControls(boolean useQuickControls) {
137        mUseQuickControls = useQuickControls;
138        mNewTab.setVisibility(mUseQuickControls ? View.GONE
139                : View.VISIBLE);
140    }
141
142    int getTabCount() {
143        return mTabMap.size();
144    }
145
146    void updateTabs(List<Tab> tabs) {
147        mTabs.clearTabs();
148        mTabMap.clear();
149        for (Tab tab : tabs) {
150            TabView tv = buildTabView(tab);
151            mTabs.addTab(tv);
152        }
153        mTabs.setSelectedTab(mTabControl.getCurrentPosition());
154    }
155
156    @Override
157    protected void onMeasure(int hspec, int vspec) {
158        super.onMeasure(hspec, vspec);
159        int w = getMeasuredWidth();
160        // adjust for new tab overlap
161        if (!mUseQuickControls) {
162            w -= mAddTabOverlap;
163        }
164        setMeasuredDimension(w, getMeasuredHeight());
165    }
166
167    @Override
168    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
169        // use paddingLeft and paddingTop
170        int pl = getPaddingLeft();
171        int pt = getPaddingTop();
172        int sw = mTabs.getMeasuredWidth();
173        int w = right - left - pl;
174        if (mUseQuickControls) {
175            mButtonWidth = 0;
176        } else {
177            mButtonWidth = mNewTab.getMeasuredWidth() - mAddTabOverlap;
178            if (w-sw < mButtonWidth) {
179                sw = w - mButtonWidth;
180            }
181        }
182        mTabs.layout(pl, pt, pl + sw, bottom - top);
183        // adjust for overlap
184        if (!mUseQuickControls) {
185            mNewTab.layout(pl + sw - mAddTabOverlap, pt,
186                    pl + sw + mButtonWidth - mAddTabOverlap, bottom - top);
187        }
188    }
189
190    public void onClick(View view) {
191        if (mNewTab == view) {
192            mUiController.openTabToHomePage();
193        } else if (mTabs.getSelectedTab() == view) {
194            if (mUseQuickControls) {
195                if (mUi.isTitleBarShowing() && !isLoading()) {
196                    mUi.stopEditingUrl();
197                    mUi.hideTitleBar();
198                } else {
199                    mUi.stopWebViewScrolling();
200                    mUi.editUrl(false, false);
201                }
202            } else if (mUi.isTitleBarShowing() && !isLoading()) {
203                mUi.stopEditingUrl();
204                mUi.hideTitleBar();
205            } else {
206                showUrlBar();
207            }
208        } else if (view instanceof TabView) {
209            final Tab tab = ((TabView) view).mTab;
210            int ix = mTabs.getChildIndex(view);
211            if (ix >= 0) {
212                mTabs.setSelectedTab(ix);
213                mUiController.switchToTab(tab);
214            }
215        }
216    }
217
218    private void showUrlBar() {
219        mUi.stopWebViewScrolling();
220        mUi.showTitleBar();
221    }
222
223    private TabView buildTabView(Tab tab) {
224        TabView tabview = new TabView(mActivity, tab);
225        mTabMap.put(tab, tabview);
226        tabview.setOnClickListener(this);
227        return tabview;
228    }
229
230    private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
231        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
232        Canvas c = new Canvas(b);
233        drawable.setBounds(0, 0, width, height);
234        drawable.draw(c);
235        c.setBitmap(null);
236        return b;
237    }
238
239    /**
240     * View used in the tab bar
241     */
242    class TabView extends LinearLayout implements OnClickListener {
243
244        Tab mTab;
245        View mTabContent;
246        TextView mTitle;
247        View mIncognito;
248        View mSnapshot;
249        ImageView mIconView;
250        ImageView mLock;
251        ImageView mClose;
252        boolean mSelected;
253        Path mPath;
254        Path mFocusPath;
255        int[] mWindowPos;
256
257        /**
258         * @param context
259         */
260        public TabView(Context context, Tab tab) {
261            super(context);
262            setWillNotDraw(false);
263            mPath = new Path();
264            mFocusPath = new Path();
265            mWindowPos = new int[2];
266            mTab = tab;
267            setGravity(Gravity.CENTER_VERTICAL);
268            setOrientation(LinearLayout.HORIZONTAL);
269            setPadding(mTabOverlap, 0, mTabSliceWidth, 0);
270            LayoutInflater inflater = LayoutInflater.from(getContext());
271            mTabContent = inflater.inflate(R.layout.tab_title, this, true);
272            mTitle = (TextView) mTabContent.findViewById(R.id.title);
273            mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
274            mLock = (ImageView) mTabContent.findViewById(R.id.lock);
275            mClose = (ImageView) mTabContent.findViewById(R.id.close);
276            mClose.setOnClickListener(this);
277            mIncognito = mTabContent.findViewById(R.id.incognito);
278            mSnapshot = mTabContent.findViewById(R.id.snapshot);
279            mSelected = false;
280            // update the status
281            updateFromTab();
282        }
283
284        @Override
285        public void onClick(View v) {
286            if (v == mClose) {
287                closeTab();
288            }
289        }
290
291        private void updateFromTab() {
292            String displayTitle = mTab.getTitle();
293            if (displayTitle == null) {
294                displayTitle = mTab.getUrl();
295            }
296            setDisplayTitle(displayTitle);
297            if (mTab.getFavicon() != null) {
298                setFavicon(mUi.getFaviconDrawable(mTab.getFavicon()));
299            }
300            updateTabIcons();
301        }
302
303        private void updateTabIcons() {
304            mIncognito.setVisibility(
305                    mTab.isPrivateBrowsingEnabled() ?
306                    View.VISIBLE : View.GONE);
307            mSnapshot.setVisibility(mTab.isSnapshot()
308                    ? View.VISIBLE : View.GONE);
309        }
310
311        @Override
312        public void setActivated(boolean selected) {
313            mSelected = selected;
314            mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
315            mIconView.setVisibility(mSelected ? View.GONE : View.VISIBLE);
316            mTitle.setTextAppearance(mActivity, mSelected ?
317                    R.style.TabTitleSelected : R.style.TabTitleUnselected);
318            setHorizontalFadingEdgeEnabled(!mSelected);
319            super.setActivated(selected);
320            updateLayoutParams();
321            setFocusable(!selected);
322            postInvalidate();
323        }
324
325        public void updateLayoutParams() {
326            LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
327            lp.width = mTabWidth;
328            lp.height =  LayoutParams.MATCH_PARENT;
329            setLayoutParams(lp);
330        }
331
332        void setDisplayTitle(String title) {
333            mTitle.setText(title);
334        }
335
336        void setFavicon(Drawable d) {
337            mIconView.setImageDrawable(d);
338        }
339
340        void setLock(Drawable d) {
341            if (null == d) {
342                mLock.setVisibility(View.GONE);
343            } else {
344                mLock.setImageDrawable(d);
345                mLock.setVisibility(View.VISIBLE);
346            }
347        }
348
349        private void closeTab() {
350            if (mTab == mTabControl.getCurrentTab()) {
351                mUiController.closeCurrentTab();
352            } else {
353                mUiController.closeTab(mTab);
354            }
355        }
356
357        @Override
358        protected void onLayout(boolean changed, int l, int t, int r, int b) {
359            super.onLayout(changed, l, t, r, b);
360            setTabPath(mPath, 0, 0, r - l, b - t);
361            setFocusPath(mFocusPath, 0, 0, r - l, b - t);
362        }
363
364        @Override
365        protected void dispatchDraw(Canvas canvas) {
366            if (mCurrentTextureWidth != mUi.getContentWidth() ||
367                    mCurrentTextureHeight != getHeight()) {
368                mCurrentTextureWidth = mUi.getContentWidth();
369                mCurrentTextureHeight = getHeight();
370
371                if (mCurrentTextureWidth > 0 && mCurrentTextureHeight > 0) {
372                    Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
373                            mCurrentTextureWidth, mCurrentTextureHeight);
374                    Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
375                            mCurrentTextureWidth, mCurrentTextureHeight);
376
377                    mActiveShader = new BitmapShader(activeTexture,
378                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
379                    mActiveShaderPaint.setShader(mActiveShader);
380
381                    mInactiveShader = new BitmapShader(inactiveTexture,
382                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
383                    mInactiveShaderPaint.setShader(mInactiveShader);
384                }
385            }
386            // add some monkey protection
387            if ((mActiveShader != null) && (mInactiveShader != null)) {
388                int state = canvas.save();
389                getLocationInWindow(mWindowPos);
390                Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
391                drawClipped(canvas, paint, mPath, mWindowPos[0]);
392                canvas.restoreToCount(state);
393            }
394            super.dispatchDraw(canvas);
395        }
396
397        private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int left) {
398            // TODO: We should change the matrix/shader only when needed
399            final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix;
400            matrix.setTranslate(-left, 0.0f);
401            (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
402            canvas.drawPath(clipPath, paint);
403            if (isFocused()) {
404                canvas.drawPath(mFocusPath, mFocusPaint);
405            }
406        }
407
408        private void setTabPath(Path path, int l, int t, int r, int b) {
409            path.reset();
410            path.moveTo(l, b);
411            path.lineTo(l, t);
412            path.lineTo(r - mTabSliceWidth, t);
413            path.lineTo(r, b);
414            path.close();
415        }
416
417        private void setFocusPath(Path path, int l, int t, int r, int b) {
418            path.reset();
419            path.moveTo(l, b);
420            path.lineTo(l, t);
421            path.lineTo(r - mTabSliceWidth, t);
422            path.lineTo(r, b);
423        }
424
425    }
426
427    private void animateTabOut(final Tab tab, final TabView tv) {
428        ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 1.0f, 0.0f);
429        ObjectAnimator scaley = ObjectAnimator.ofFloat(tv, "scaleY", 1.0f, 0.0f);
430        ObjectAnimator alpha = ObjectAnimator.ofFloat(tv, "alpha", 1.0f, 0.0f);
431        AnimatorSet animator = new AnimatorSet();
432        animator.playTogether(scalex, scaley, alpha);
433        animator.setDuration(150);
434        animator.addListener(new AnimatorListener() {
435
436            @Override
437            public void onAnimationCancel(Animator animation) {
438            }
439
440            @Override
441            public void onAnimationEnd(Animator animation) {
442                mTabs.removeTab(tv);
443                mTabMap.remove(tab);
444                mUi.onRemoveTabCompleted(tab);
445            }
446
447            @Override
448            public void onAnimationRepeat(Animator animation) {
449            }
450
451            @Override
452            public void onAnimationStart(Animator animation) {
453            }
454
455        });
456        animator.start();
457    }
458
459    private void animateTabIn(final Tab tab, final TabView tv) {
460        ObjectAnimator scalex = ObjectAnimator.ofFloat(tv, "scaleX", 0.0f, 1.0f);
461        scalex.setDuration(150);
462        scalex.addListener(new AnimatorListener() {
463
464            @Override
465            public void onAnimationCancel(Animator animation) {
466            }
467
468            @Override
469            public void onAnimationEnd(Animator animation) {
470                mUi.onAddTabCompleted(tab);
471            }
472
473            @Override
474            public void onAnimationRepeat(Animator animation) {
475            }
476
477            @Override
478            public void onAnimationStart(Animator animation) {
479                mTabs.addTab(tv);
480            }
481
482        });
483        scalex.start();
484    }
485
486    // TabChangeListener implementation
487
488    public void onSetActiveTab(Tab tab) {
489        mTabs.setSelectedTab(mTabControl.getTabPosition(tab));
490    }
491
492    public void onFavicon(Tab tab, Bitmap favicon) {
493        TabView tv = mTabMap.get(tab);
494        if (tv != null) {
495            tv.setFavicon(mUi.getFaviconDrawable(favicon));
496        }
497    }
498
499    public void onNewTab(Tab tab) {
500        TabView tv = buildTabView(tab);
501        animateTabIn(tab, tv);
502    }
503
504    public void onRemoveTab(Tab tab) {
505        TabView tv = mTabMap.get(tab);
506        if (tv != null) {
507            animateTabOut(tab, tv);
508        } else {
509            mTabMap.remove(tab);
510        }
511    }
512
513    public void onUrlAndTitle(Tab tab, String url, String title) {
514        TabView tv = mTabMap.get(tab);
515        if (tv != null) {
516            if (title != null) {
517                tv.setDisplayTitle(title);
518            } else if (url != null) {
519                tv.setDisplayTitle(UrlUtils.stripUrl(url));
520            }
521            tv.updateTabIcons();
522        }
523    }
524
525    private boolean isLoading() {
526        Tab tab = mTabControl.getCurrentTab();
527        if (tab != null) {
528            return tab.inPageLoad();
529        } else {
530            return false;
531        }
532    }
533
534}
535