blob: d331b53649c5a18c1f2ae4f186e09b100a32c57e [file] [log] [blame]
Michael Kolb370a4f32010-10-06 10:45:32 -07001/*
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 android.content.Context;
John Reckb3417f02011-01-14 11:01:05 -080020import android.content.res.TypedArray;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040021import android.graphics.drawable.Drawable;
Leon Scroggins74dbe012010-10-15 10:54:27 -040022import android.text.TextUtils;
Michael Kolb370a4f32010-10-06 10:45:32 -070023import android.util.AttributeSet;
Michael Kolb5a72f182011-01-13 20:35:06 -080024import android.util.TypedValue;
Michael Kolb370a4f32010-10-06 10:45:32 -070025import android.view.Gravity;
26import android.view.View;
27import android.view.View.OnClickListener;
28import android.widget.ImageButton;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31import android.widget.TextView;
32
33import java.util.ArrayList;
34import java.util.List;
35
36/**
37 * Simple bread crumb view
38 * Use setController to receive callbacks from user interactions
39 * Use pushView, popView, clear, and getTopData to change/access the view stack
40 */
41public class BreadCrumbView extends LinearLayout implements OnClickListener {
John Reckb3417f02011-01-14 11:01:05 -080042 private static final int DIVIDER_PADDING = 12; // dips
John Reck95f88e42011-09-26 09:25:43 -070043 private static final int CRUMB_PADDING = 8; // dips
Michael Kolb370a4f32010-10-06 10:45:32 -070044
John Reck71efc2b2011-05-09 16:54:28 -070045 public interface Controller {
46 public void onTop(BreadCrumbView view, int level, Object data);
Michael Kolb370a4f32010-10-06 10:45:32 -070047 }
48
49 private ImageButton mBackButton;
50 private Controller mController;
51 private List<Crumb> mCrumbs;
52 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040053 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080054 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080055 private int mMaxVisible = -1;
John Reck71efc2b2011-05-09 16:54:28 -070056 private Context mContext;
John Reck95f88e42011-09-26 09:25:43 -070057 private int mCrumbPadding;
Michael Kolb370a4f32010-10-06 10:45:32 -070058
59 /**
60 * @param context
61 * @param attrs
62 * @param defStyle
63 */
64 public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
65 super(context, attrs, defStyle);
66 init(context);
67 }
68
69 /**
70 * @param context
71 * @param attrs
72 */
73 public BreadCrumbView(Context context, AttributeSet attrs) {
74 super(context, attrs);
75 init(context);
76 }
77
78 /**
79 * @param context
80 */
81 public BreadCrumbView(Context context) {
82 super(context);
83 init(context);
84 }
85
86 private void init(Context ctx) {
John Reck71efc2b2011-05-09 16:54:28 -070087 mContext = ctx;
88 setFocusable(true);
Michael Kolb370a4f32010-10-06 10:45:32 -070089 mUseBackButton = false;
90 mCrumbs = new ArrayList<Crumb>();
John Reck71efc2b2011-05-09 16:54:28 -070091 TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
John Reckb3417f02011-01-14 11:01:05 -080092 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
93 a.recycle();
John Reck95f88e42011-09-26 09:25:43 -070094 float density = mContext.getResources().getDisplayMetrics().density;
95 mDividerPadding = DIVIDER_PADDING * density;
96 mCrumbPadding = (int) (CRUMB_PADDING * density);
John Reck89f73c12010-12-01 10:10:14 -080097 addBackButton();
Michael Kolb370a4f32010-10-06 10:45:32 -070098 }
99
100 public void setUseBackButton(boolean useflag) {
101 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -0800102 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700103 }
104
105 public void setController(Controller ctl) {
106 mController = ctl;
107 }
108
John Reck89f73c12010-12-01 10:10:14 -0800109 public int getMaxVisible() {
110 return mMaxVisible;
111 }
112
113 public void setMaxVisible(int max) {
114 mMaxVisible = max;
115 updateVisible();
116 }
117
118 public int getTopLevel() {
119 return mCrumbs.size();
120 }
121
Michael Kolb370a4f32010-10-06 10:45:32 -0700122 public Object getTopData() {
123 Crumb c = getTopCrumb();
124 if (c != null) {
125 return c.data;
126 }
127 return null;
128 }
129
130 public int size() {
131 return mCrumbs.size();
132 }
133
134 public void clear() {
135 while (mCrumbs.size() > 1) {
136 pop(false);
137 }
138 pop(true);
139 }
140
141 public void notifyController() {
142 if (mController != null) {
143 if (mCrumbs.size() > 0) {
John Reck71efc2b2011-05-09 16:54:28 -0700144 mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700145 } else {
John Reck71efc2b2011-05-09 16:54:28 -0700146 mController.onTop(this, 0, null);
Michael Kolb370a4f32010-10-06 10:45:32 -0700147 }
148 }
149 }
150
Leon Scroggins905250c2010-12-17 15:25:33 -0500151 public View pushView(String name, Object data) {
152 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400153 }
154
Leon Scroggins905250c2010-12-17 15:25:33 -0500155 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400156 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700157 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500158 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700159 }
160
161 public void pushView(View view, Object data) {
162 Crumb crumb = new Crumb(view, true, data);
163 pushCrumb(crumb);
164 }
165
166 public void popView() {
167 pop(true);
168 }
169
170 private void addBackButton() {
171 mBackButton = new ImageButton(mContext);
Michael Kolb5a72f182011-01-13 20:35:06 -0800172 mBackButton.setImageResource(R.drawable.ic_back_hierarchy_holo_dark);
173 TypedValue outValue = new TypedValue();
174 getContext().getTheme().resolveAttribute(
175 android.R.attr.selectableItemBackground, outValue, true);
176 int resid = outValue.resourceId;
177 mBackButton.setBackgroundResource(resid);
Michael Kolb370a4f32010-10-06 10:45:32 -0700178 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
179 LayoutParams.MATCH_PARENT));
180 mBackButton.setOnClickListener(this);
Leon Scroggins905250c2010-12-17 15:25:33 -0500181 mBackButton.setVisibility(View.GONE);
John Reckaac2ce02012-09-21 14:17:47 -0700182 mBackButton.setContentDescription(mContext.getText(
183 R.string.accessibility_button_bookmarks_folder_up));
Michael Kolb370a4f32010-10-06 10:45:32 -0700184 addView(mBackButton, 0);
185 }
186
187 private void pushCrumb(Crumb crumb) {
John Reck89f73c12010-12-01 10:10:14 -0800188 if (mCrumbs.size() > 0) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700189 addSeparator();
190 }
191 mCrumbs.add(crumb);
192 addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800193 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700194 crumb.crumbView.setOnClickListener(this);
195 }
196
197 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800198 View sep = makeDividerView();
199 sep.setLayoutParams(makeDividerLayoutParams());
Michael Kolb370a4f32010-10-06 10:45:32 -0700200 addView(sep);
201 }
202
John Reckb3417f02011-01-14 11:01:05 -0800203 private ImageView makeDividerView() {
204 ImageView result = new ImageView(mContext);
205 result.setImageDrawable(mSeparatorDrawable);
206 result.setScaleType(ImageView.ScaleType.FIT_XY);
207 return result;
208 }
209
210 private LayoutParams makeDividerLayoutParams() {
211 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
212 LayoutParams.MATCH_PARENT);
213 params.topMargin = (int) mDividerPadding;
214 params.bottomMargin = (int) mDividerPadding;
215 return params;
216 }
217
Michael Kolb370a4f32010-10-06 10:45:32 -0700218 private void pop(boolean notify) {
219 int n = mCrumbs.size();
220 if (n > 0) {
221 removeLastView();
222 if (!mUseBackButton || (n > 1)) {
223 // remove separator
224 removeLastView();
225 }
226 mCrumbs.remove(n - 1);
227 if (mUseBackButton) {
228 Crumb top = getTopCrumb();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400229 if (top != null && top.canGoBack) {
230 mBackButton.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700231 } else {
Leon Scroggins905250c2010-12-17 15:25:33 -0500232 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700233 }
234 }
John Reck89f73c12010-12-01 10:10:14 -0800235 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700236 if (notify) {
237 notifyController();
238 }
239 }
240 }
241
John Reck89f73c12010-12-01 10:10:14 -0800242 private void updateVisible() {
243 // start at index 1 (0 == back button)
244 int childIndex = 1;
245 if (mMaxVisible >= 0) {
246 int invisibleCrumbs = size() - mMaxVisible;
247 if (invisibleCrumbs > 0) {
248 int crumbIndex = 0;
249 while (crumbIndex < invisibleCrumbs) {
250 // Set the crumb to GONE.
251 getChildAt(childIndex).setVisibility(View.GONE);
252 childIndex++;
253 // Each crumb is followed by a separator (except the last
254 // one). Also make it GONE
255 if (getChildAt(childIndex) != null) {
256 getChildAt(childIndex).setVisibility(View.GONE);
257 }
258 childIndex++;
259 // Move to the next crumb.
260 crumbIndex++;
261 }
262 }
263 // Make sure the last two are visible.
264 int childCount = getChildCount();
265 while (childIndex < childCount) {
266 getChildAt(childIndex).setVisibility(View.VISIBLE);
267 childIndex++;
268 }
269 } else {
270 int count = getChildCount();
271 for (int i = childIndex; i < count ; i++) {
272 getChildAt(i).setVisibility(View.VISIBLE);
273 }
274 }
275 if (mUseBackButton) {
276 boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
Leon Scroggins905250c2010-12-17 15:25:33 -0500277 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800278 } else {
279 mBackButton.setVisibility(View.GONE);
280 }
281 }
282
Michael Kolb370a4f32010-10-06 10:45:32 -0700283 private void removeLastView() {
284 int ix = getChildCount();
285 if (ix > 0) {
286 removeViewAt(ix-1);
287 }
288 }
289
John Reckd4893b02010-12-07 17:38:34 -0800290 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700291 Crumb crumb = null;
292 if (mCrumbs.size() > 0) {
293 crumb = mCrumbs.get(mCrumbs.size() - 1);
294 }
295 return crumb;
296 }
297
298 @Override
299 public void onClick(View v) {
300 if (mBackButton == v) {
301 popView();
302 notifyController();
303 } else {
304 // pop until view matches crumb view
305 while (v != getTopCrumb().crumbView) {
306 pop(false);
307 }
308 notifyController();
309 }
310 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400311 @Override
312 public int getBaseline() {
313 int ix = getChildCount();
314 if (ix > 0) {
315 // If there is at least one crumb, the baseline will be its
316 // baseline.
317 return getChildAt(ix-1).getBaseline();
318 }
319 return super.getBaseline();
320 }
321
322 @Override
323 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
324 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
325 int height = mSeparatorDrawable.getIntrinsicHeight();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800326 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400327 // This should only be an issue if there are currently no separators
328 // showing; i.e. if there is one crumb and no back button.
329 int mode = View.MeasureSpec.getMode(heightMeasureSpec);
330 switch(mode) {
331 case View.MeasureSpec.AT_MOST:
332 if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
333 return;
334 }
335 break;
336 case View.MeasureSpec.EXACTLY:
337 return;
338 default:
339 break;
340 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800341 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400342 }
343 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700344
345 class Crumb {
346
347 public View crumbView;
348 public boolean canGoBack;
349 public Object data;
350
351 public Crumb(String title, boolean backEnabled, Object tag) {
352 init(makeCrumbView(title), backEnabled, tag);
353 }
354
355 public Crumb(View view, boolean backEnabled, Object tag) {
356 init(view, backEnabled, tag);
357 }
358
359 private void init(View view, boolean back, Object tag) {
360 canGoBack = back;
361 crumbView = view;
362 data = tag;
363 }
364
365 private TextView makeCrumbView(String name) {
366 TextView tv = new TextView(mContext);
367 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
John Reck95f88e42011-09-26 09:25:43 -0700368 tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
Michael Kolb370a4f32010-10-06 10:45:32 -0700369 tv.setGravity(Gravity.CENTER_VERTICAL);
370 tv.setText(name);
371 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
372 LayoutParams.MATCH_PARENT));
John Reck95f88e42011-09-26 09:25:43 -0700373 tv.setSingleLine();
Leon Scroggins74dbe012010-10-15 10:54:27 -0400374 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700375 return tv;
376 }
377
378 }
379
380}