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