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