blob: 9aaecd70002cca525efb5625d3d99172808bf66b [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
Michael Kolb370a4f32010-10-06 10:45:32 -070043
44 interface Controller {
45 public void onTop(int level, Object data);
46 }
47
48 private ImageButton mBackButton;
49 private Controller mController;
50 private List<Crumb> mCrumbs;
51 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040052 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080053 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080054 private int mMaxVisible = -1;
Michael Kolb370a4f32010-10-06 10:45:32 -070055
56 /**
57 * @param context
58 * @param attrs
59 * @param defStyle
60 */
61 public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
62 super(context, attrs, defStyle);
63 init(context);
64 }
65
66 /**
67 * @param context
68 * @param attrs
69 */
70 public BreadCrumbView(Context context, AttributeSet attrs) {
71 super(context, attrs);
72 init(context);
73 }
74
75 /**
76 * @param context
77 */
78 public BreadCrumbView(Context context) {
79 super(context);
80 init(context);
81 }
82
83 private void init(Context ctx) {
84 mUseBackButton = false;
85 mCrumbs = new ArrayList<Crumb>();
John Reckb3417f02011-01-14 11:01:05 -080086 TypedArray a = ctx.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
87 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
88 a.recycle();
89 mDividerPadding = DIVIDER_PADDING * ctx.getResources().getDisplayMetrics().density;
John Reck89f73c12010-12-01 10:10:14 -080090 addBackButton();
Michael Kolb370a4f32010-10-06 10:45:32 -070091 }
92
93 public void setUseBackButton(boolean useflag) {
94 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -080095 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -070096 }
97
98 public void setController(Controller ctl) {
99 mController = ctl;
100 }
101
John Reck89f73c12010-12-01 10:10:14 -0800102 public int getMaxVisible() {
103 return mMaxVisible;
104 }
105
106 public void setMaxVisible(int max) {
107 mMaxVisible = max;
108 updateVisible();
109 }
110
111 public int getTopLevel() {
112 return mCrumbs.size();
113 }
114
Michael Kolb370a4f32010-10-06 10:45:32 -0700115 public Object getTopData() {
116 Crumb c = getTopCrumb();
117 if (c != null) {
118 return c.data;
119 }
120 return null;
121 }
122
123 public int size() {
124 return mCrumbs.size();
125 }
126
127 public void clear() {
128 while (mCrumbs.size() > 1) {
129 pop(false);
130 }
131 pop(true);
132 }
133
134 public void notifyController() {
135 if (mController != null) {
136 if (mCrumbs.size() > 0) {
137 mController.onTop(mCrumbs.size(), getTopCrumb().data);
138 } else {
139 mController.onTop(0, null);
140 }
141 }
142 }
143
Leon Scroggins905250c2010-12-17 15:25:33 -0500144 public View pushView(String name, Object data) {
145 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400146 }
147
Leon Scroggins905250c2010-12-17 15:25:33 -0500148 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400149 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700150 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500151 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700152 }
153
154 public void pushView(View view, Object data) {
155 Crumb crumb = new Crumb(view, true, data);
156 pushCrumb(crumb);
157 }
158
159 public void popView() {
160 pop(true);
161 }
162
163 private void addBackButton() {
164 mBackButton = new ImageButton(mContext);
Michael Kolb5a72f182011-01-13 20:35:06 -0800165 mBackButton.setImageResource(R.drawable.ic_back_hierarchy_holo_dark);
166 TypedValue outValue = new TypedValue();
167 getContext().getTheme().resolveAttribute(
168 android.R.attr.selectableItemBackground, outValue, true);
169 int resid = outValue.resourceId;
170 mBackButton.setBackgroundResource(resid);
Michael Kolb370a4f32010-10-06 10:45:32 -0700171 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
172 LayoutParams.MATCH_PARENT));
173 mBackButton.setOnClickListener(this);
Leon Scroggins905250c2010-12-17 15:25:33 -0500174 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700175 addView(mBackButton, 0);
176 }
177
178 private void pushCrumb(Crumb crumb) {
John Reck89f73c12010-12-01 10:10:14 -0800179 if (mCrumbs.size() > 0) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700180 addSeparator();
181 }
182 mCrumbs.add(crumb);
183 addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800184 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700185 crumb.crumbView.setOnClickListener(this);
186 }
187
188 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800189 View sep = makeDividerView();
190 sep.setLayoutParams(makeDividerLayoutParams());
Michael Kolb370a4f32010-10-06 10:45:32 -0700191 addView(sep);
192 }
193
John Reckb3417f02011-01-14 11:01:05 -0800194 private ImageView makeDividerView() {
195 ImageView result = new ImageView(mContext);
196 result.setImageDrawable(mSeparatorDrawable);
197 result.setScaleType(ImageView.ScaleType.FIT_XY);
198 return result;
199 }
200
201 private LayoutParams makeDividerLayoutParams() {
202 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
203 LayoutParams.MATCH_PARENT);
204 params.topMargin = (int) mDividerPadding;
205 params.bottomMargin = (int) mDividerPadding;
206 return params;
207 }
208
Michael Kolb370a4f32010-10-06 10:45:32 -0700209 private void pop(boolean notify) {
210 int n = mCrumbs.size();
211 if (n > 0) {
212 removeLastView();
213 if (!mUseBackButton || (n > 1)) {
214 // remove separator
215 removeLastView();
216 }
217 mCrumbs.remove(n - 1);
218 if (mUseBackButton) {
219 Crumb top = getTopCrumb();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400220 if (top != null && top.canGoBack) {
221 mBackButton.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700222 } else {
Leon Scroggins905250c2010-12-17 15:25:33 -0500223 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700224 }
225 }
John Reck89f73c12010-12-01 10:10:14 -0800226 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700227 if (notify) {
228 notifyController();
229 }
230 }
231 }
232
John Reck89f73c12010-12-01 10:10:14 -0800233 private void updateVisible() {
234 // start at index 1 (0 == back button)
235 int childIndex = 1;
236 if (mMaxVisible >= 0) {
237 int invisibleCrumbs = size() - mMaxVisible;
238 if (invisibleCrumbs > 0) {
239 int crumbIndex = 0;
240 while (crumbIndex < invisibleCrumbs) {
241 // Set the crumb to GONE.
242 getChildAt(childIndex).setVisibility(View.GONE);
243 childIndex++;
244 // Each crumb is followed by a separator (except the last
245 // one). Also make it GONE
246 if (getChildAt(childIndex) != null) {
247 getChildAt(childIndex).setVisibility(View.GONE);
248 }
249 childIndex++;
250 // Move to the next crumb.
251 crumbIndex++;
252 }
253 }
254 // Make sure the last two are visible.
255 int childCount = getChildCount();
256 while (childIndex < childCount) {
257 getChildAt(childIndex).setVisibility(View.VISIBLE);
258 childIndex++;
259 }
260 } else {
261 int count = getChildCount();
262 for (int i = childIndex; i < count ; i++) {
263 getChildAt(i).setVisibility(View.VISIBLE);
264 }
265 }
266 if (mUseBackButton) {
267 boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
Leon Scroggins905250c2010-12-17 15:25:33 -0500268 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800269 } else {
270 mBackButton.setVisibility(View.GONE);
271 }
272 }
273
Michael Kolb370a4f32010-10-06 10:45:32 -0700274 private void removeLastView() {
275 int ix = getChildCount();
276 if (ix > 0) {
277 removeViewAt(ix-1);
278 }
279 }
280
John Reckd4893b02010-12-07 17:38:34 -0800281 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700282 Crumb crumb = null;
283 if (mCrumbs.size() > 0) {
284 crumb = mCrumbs.get(mCrumbs.size() - 1);
285 }
286 return crumb;
287 }
288
289 @Override
290 public void onClick(View v) {
291 if (mBackButton == v) {
292 popView();
293 notifyController();
294 } else {
295 // pop until view matches crumb view
296 while (v != getTopCrumb().crumbView) {
297 pop(false);
298 }
299 notifyController();
300 }
301 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400302 @Override
303 public int getBaseline() {
304 int ix = getChildCount();
305 if (ix > 0) {
306 // If there is at least one crumb, the baseline will be its
307 // baseline.
308 return getChildAt(ix-1).getBaseline();
309 }
310 return super.getBaseline();
311 }
312
313 @Override
314 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
315 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
316 int height = mSeparatorDrawable.getIntrinsicHeight();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800317 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400318 // This should only be an issue if there are currently no separators
319 // showing; i.e. if there is one crumb and no back button.
320 int mode = View.MeasureSpec.getMode(heightMeasureSpec);
321 switch(mode) {
322 case View.MeasureSpec.AT_MOST:
323 if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
324 return;
325 }
326 break;
327 case View.MeasureSpec.EXACTLY:
328 return;
329 default:
330 break;
331 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800332 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400333 }
334 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700335
336 class Crumb {
337
338 public View crumbView;
339 public boolean canGoBack;
340 public Object data;
341
342 public Crumb(String title, boolean backEnabled, Object tag) {
343 init(makeCrumbView(title), backEnabled, tag);
344 }
345
346 public Crumb(View view, boolean backEnabled, Object tag) {
347 init(view, backEnabled, tag);
348 }
349
350 private void init(View view, boolean back, Object tag) {
351 canGoBack = back;
352 crumbView = view;
353 data = tag;
354 }
355
356 private TextView makeCrumbView(String name) {
357 TextView tv = new TextView(mContext);
358 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
359 tv.setPadding(16, 0, 16, 0);
360 tv.setGravity(Gravity.CENTER_VERTICAL);
361 tv.setText(name);
362 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
363 LayoutParams.MATCH_PARENT));
Leon Scroggins74dbe012010-10-15 10:54:27 -0400364 tv.setMaxWidth(mContext.getResources().getInteger(
365 R.integer.max_width_crumb));
366 tv.setMaxLines(1);
367 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700368 return tv;
369 }
370
371 }
372
373}