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