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