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