TabBar.java revision f558f0d9372ecf4eeba86dd52bf67f38ff79c0b8
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(0, (int) res.getDimension(R.dimen.tab_padding_top), 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
120        updateTabs(mUiController.getTabs());
121
122        mVisibleTitleHeight = 1;
123        mButtonWidth = -1;
124        // tab dimensions
125        mTabOverlap = (int) res.getDimension(R.dimen.tab_overlap);
126        mTabSliceWidth = (int) res.getDimension(R.dimen.tab_slice);
127        mTabPadding = (int) res.getDimension(R.dimen.tab_padding);
128
129        mActiveShaderPaint.setStyle(Paint.Style.FILL);
130        mActiveShaderPaint.setAntiAlias(true);
131
132        mInactiveShaderPaint.setStyle(Paint.Style.FILL);
133        mInactiveShaderPaint.setAntiAlias(true);
134
135    }
136
137    void setUseQuickControls(boolean useQuickControls) {
138        mUseQuickControls = useQuickControls;
139        mNewTab.setVisibility(mUseQuickControls ? View.GONE
140                : View.VISIBLE);
141    }
142
143    int getTabCount() {
144        return mTabMap.size();
145    }
146
147    void updateTabs(List<Tab> tabs) {
148        mTabs.clearTabs();
149        mTabMap.clear();
150        for (Tab tab : tabs) {
151            TabView tv = buildTabView(tab);
152        }
153        mTabs.setSelectedTab(mTabControl.getCurrentIndex());
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 -= mTabOverlap;
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() - mTabOverlap;
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 - mTabOverlap, pt,
186                    pl + sw + mButtonWidth - mTabOverlap, bottom - top);
187        }
188    }
189
190    public void onClick(View view) {
191        mUi.hideComboView();
192        if (mNewTab == view) {
193            mUiController.openTabToHomePage();
194        } else if (mTabs.getSelectedTab() == view) {
195            if (mUseQuickControls) {
196                if (mUi.isFakeTitleBarShowing() && !isLoading()) {
197                    mUi.hideFakeTitleBar();
198                } else {
199                    mUi.stopWebViewScrolling();
200                    mUi.showFakeTitleBarAndEdit();
201                }
202            } else if (mUi.isFakeTitleBarShowing() && !isLoading()) {
203                mUi.hideFakeTitleBar();
204            } else {
205                showUrlBar();
206            }
207        } else {
208            int ix = mTabs.getChildIndex(view);
209            if (ix >= 0) {
210                mTabs.setSelectedTab(ix);
211                mUiController.switchToTab(ix);
212            }
213        }
214    }
215
216    private void showUrlBar() {
217        mUi.stopWebViewScrolling();
218        mUi.showFakeTitleBar();
219    }
220
221    void showTitleBarIndicator(boolean show) {
222        Tab tab = mTabControl.getCurrentTab();
223        if (tab != null) {
224            TabView tv = mTabMap.get(tab);
225            if (tv != null) {
226                tv.showIndicator(show);
227            }
228        }
229    }
230
231    // callback after fake titlebar is shown
232    void onShowTitleBar() {
233        showTitleBarIndicator(false);
234    }
235
236    // callback after fake titlebar is hidden
237    void onHideTitleBar() {
238        showTitleBarIndicator(mVisibleTitleHeight == 0);
239        Tab tab = mTabControl.getCurrentTab();
240        tab.getWebView().requestFocus();
241    }
242
243    // webview scroll listener
244
245    @Override
246    public void onScroll(int visibleTitleHeight) {
247        if (mUseQuickControls) return;
248        // isLoading is using the current tab, which initially might not be set yet
249        if (mTabControl.getCurrentTab() != null
250                && !isLoading()) {
251            if (visibleTitleHeight == 0) {
252                mUi.hideFakeTitleBar();
253                showTitleBarIndicator(true);
254            } else {
255                showTitleBarIndicator(false);
256            }
257        }
258        mVisibleTitleHeight = visibleTitleHeight;
259    }
260
261    @Override
262    public void createContextMenu(ContextMenu menu) {
263        MenuInflater inflater = mActivity.getMenuInflater();
264        inflater.inflate(R.menu.title_context, menu);
265        mActivity.onCreateContextMenu(menu, this, null);
266    }
267
268    private TabView buildTabView(Tab tab) {
269        TabView tabview = new TabView(mActivity, tab);
270        mTabMap.put(tab, tabview);
271        tabview.setOnClickListener(this);
272        mTabs.addTab(tabview);
273        return tabview;
274    }
275
276    private static Bitmap getDrawableAsBitmap(Drawable drawable, int width, int height) {
277        Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
278        Canvas c = new Canvas(b);
279        drawable.setBounds(0, 0, width, height);
280        drawable.draw(c);
281        return b;
282    }
283
284    /**
285     * View used in the tab bar
286     */
287    class TabView extends LinearLayout implements OnClickListener {
288
289        Tab mTab;
290        View mTabContent;
291        TextView mTitle;
292        View mIndicator;
293        View mIncognito;
294        ImageView mIconView;
295        ImageView mLock;
296        ImageView mClose;
297        boolean mSelected;
298        boolean mInLoad;
299        Path mPath;
300        int[] mWindowPos;
301
302        /**
303         * @param context
304         */
305        public TabView(Context context, Tab tab) {
306            super(context);
307            setWillNotDraw(false);
308            mPath = new Path();
309            mWindowPos = new int[2];
310            mTab = tab;
311            setGravity(Gravity.CENTER_VERTICAL);
312            setOrientation(LinearLayout.HORIZONTAL);
313            setPadding(mTabPadding, 0, 0, 0);
314            LayoutInflater inflater = LayoutInflater.from(getContext());
315            mTabContent = inflater.inflate(R.layout.tab_title, this, true);
316            mTitle = (TextView) mTabContent.findViewById(R.id.title);
317            mIconView = (ImageView) mTabContent.findViewById(R.id.favicon);
318            mLock = (ImageView) mTabContent.findViewById(R.id.lock);
319            mClose = (ImageView) mTabContent.findViewById(R.id.close);
320            mClose.setOnClickListener(this);
321            mIncognito = mTabContent.findViewById(R.id.incognito);
322            mIndicator = mTabContent.findViewById(R.id.chevron);
323            mSelected = false;
324            mInLoad = false;
325            // update the status
326            updateFromTab();
327        }
328
329        void showIndicator(boolean show) {
330            if (mSelected) {
331                mIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
332            } else {
333                mIndicator.setVisibility(View.GONE);
334            }
335        }
336
337        @Override
338        public void onClick(View v) {
339            if (v == mClose) {
340                closeTab();
341            }
342        }
343
344        private void updateFromTab() {
345            String displayTitle = mTab.getTitle();
346            if (displayTitle == null) {
347                displayTitle = mTab.getUrl();
348            }
349            setDisplayTitle(displayTitle);
350            setProgress(mTab.getLoadProgress());
351            if (mTab.getFavicon() != null) {
352                setFavicon(renderFavicon(mTab.getFavicon()));
353            }
354            if (mTab != null) {
355                mIncognito.setVisibility(
356                        mTab.isPrivateBrowsingEnabled() ?
357                        View.VISIBLE : View.GONE);
358            }
359        }
360
361        @Override
362        public void setActivated(boolean selected) {
363            mSelected = selected;
364            mClose.setVisibility(mSelected ? View.VISIBLE : View.GONE);
365            mIndicator.setVisibility(View.GONE);
366            mTitle.setTextAppearance(mActivity, mSelected ?
367                    R.style.TabTitleSelected : R.style.TabTitleUnselected);
368            setHorizontalFadingEdgeEnabled(!mSelected);
369            super.setActivated(selected);
370            LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
371            lp.width = selected ? mTabWidthSelected : mTabWidthUnselected;
372            lp.height =  LayoutParams.MATCH_PARENT;
373            setLayoutParams(lp);
374        }
375
376        void setDisplayTitle(String title) {
377            mTitle.setText(title);
378        }
379
380        void setFavicon(Drawable d) {
381            mIconView.setImageDrawable(d);
382        }
383
384        void setLock(Drawable d) {
385            if (null == d) {
386                mLock.setVisibility(View.GONE);
387            } else {
388                mLock.setImageDrawable(d);
389                mLock.setVisibility(View.VISIBLE);
390            }
391        }
392
393        void setProgress(int newProgress) {
394            if (newProgress >= PROGRESS_MAX) {
395                mInLoad = false;
396            } else {
397                if (!mInLoad && getWindowToken() != null) {
398                    mInLoad = true;
399                }
400            }
401        }
402
403        private void closeTab() {
404            if (mTab == mTabControl.getCurrentTab()) {
405                mUiController.closeCurrentTab();
406            } else {
407                mUiController.closeTab(mTab);
408            }
409        }
410
411        @Override
412        protected void onLayout(boolean changed, int l, int t, int r, int b) {
413            super.onLayout(changed, l, t, r, b);
414            setTabPath(mPath, 0, 0, r - l, b - t);
415        }
416
417        @Override
418        protected void dispatchDraw(Canvas canvas) {
419            if (mCurrentTextureWidth != mUi.getContentWidth() ||
420                    mCurrentTextureHeight != getHeight()) {
421                mCurrentTextureWidth = mUi.getContentWidth();
422                mCurrentTextureHeight = getHeight();
423
424                if (mCurrentTextureWidth > 0 && mCurrentTextureHeight > 0) {
425                    Bitmap activeTexture = getDrawableAsBitmap(mActiveDrawable,
426                            mCurrentTextureWidth, mCurrentTextureHeight);
427                    Bitmap inactiveTexture = getDrawableAsBitmap(mInactiveDrawable,
428                            mCurrentTextureWidth, mCurrentTextureHeight);
429
430                    mActiveShader = new BitmapShader(activeTexture,
431                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
432                    mActiveShaderPaint.setShader(mActiveShader);
433
434                    mInactiveShader = new BitmapShader(inactiveTexture,
435                            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
436                    mInactiveShaderPaint.setShader(mInactiveShader);
437                }
438            }
439
440            int state = canvas.save();
441            getLocationInWindow(mWindowPos);
442            Paint paint = mSelected ? mActiveShaderPaint : mInactiveShaderPaint;
443            drawClipped(canvas, paint, mPath, mWindowPos[0]);
444            canvas.restoreToCount(state);
445            super.dispatchDraw(canvas);
446        }
447
448        private void drawClipped(Canvas canvas, Paint paint, Path clipPath, int left) {
449            // TODO: We should change the matrix/shader only when needed
450            final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix;
451            matrix.setTranslate(-left, 0.0f);
452            (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix);
453            canvas.drawPath(clipPath, paint);
454        }
455
456        private void setTabPath(Path path, int l, int t, int r, int b) {
457            path.reset();
458            path.moveTo(l, b);
459            path.lineTo(l, t);
460            path.lineTo(r - mTabSliceWidth, t);
461            path.lineTo(r, b);
462            path.close();
463        }
464
465    }
466
467    private Drawable renderFavicon(Bitmap icon) {
468        Drawable[] array = new Drawable[3];
469        array[0] = new PaintDrawable(Color.BLACK);
470        array[1] = new PaintDrawable(Color.WHITE);
471        if (icon == null) {
472            array[2] = mGenericFavicon;
473        } else {
474            array[2] = new BitmapDrawable(icon);
475        }
476        LayerDrawable d = new LayerDrawable(array);
477        d.setLayerInset(1, 1, 1, 1, 1);
478        d.setLayerInset(2, 2, 2, 2, 2);
479        return d;
480    }
481
482    // TabChangeListener implementation
483
484    public void onSetActiveTab(Tab tab) {
485        mTabs.setSelectedTab(mTabControl.getTabIndex(tab));
486        TabView tv = mTabMap.get(tab);
487        if (tv != null) {
488            tv.setProgress(tv.mTab.getLoadProgress());
489            // update the scroll state
490            WebView webview = tab.getWebView();
491            if (webview != null) {
492                int h = webview.getVisibleTitleHeight();
493                mVisibleTitleHeight = h -1;
494                onScroll(h);
495            }
496        }
497    }
498
499    public void onFavicon(Tab tab, Bitmap favicon) {
500        TabView tv = mTabMap.get(tab);
501        if (tv != null) {
502            tv.setFavicon(renderFavicon(favicon));
503        }
504    }
505
506    public void onNewTab(Tab tab) {
507        TabView tv = buildTabView(tab);
508    }
509
510    public void onProgress(Tab tab, int progress) {
511        TabView tv = mTabMap.get(tab);
512        if (tv != null) {
513            tv.setProgress(progress);
514        }
515    }
516
517    public void onRemoveTab(Tab tab) {
518        TabView tv = mTabMap.get(tab);
519        if (tv != null) {
520            mTabs.removeTab(tv);
521        }
522        mTabMap.remove(tab);
523    }
524
525    public void onUrlAndTitle(Tab tab, String url, String title) {
526        TabView tv = mTabMap.get(tab);
527        if (tv != null) {
528            if (title != null) {
529                tv.setDisplayTitle(title);
530            } else if (url != null) {
531                tv.setDisplayTitle(UrlUtils.stripUrl(url));
532            }
533        }
534    }
535
536    private boolean isLoading() {
537        TabView tv = mTabMap.get(mTabControl.getCurrentTab());
538        if (tv != null) {
539            return tv.mInLoad;
540        } else {
541            return false;
542        }
543    }
544
545}
546