blob: b8409f09f6cda3a98520eb7c648ad5f9b6d3d3e8 [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;
24import android.view.Gravity;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.widget.ImageButton;
28import android.widget.ImageView;
29import android.widget.LinearLayout;
30import android.widget.TextView;
31
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * Simple bread crumb view
37 * Use setController to receive callbacks from user interactions
38 * Use pushView, popView, clear, and getTopData to change/access the view stack
39 */
40public class BreadCrumbView extends LinearLayout implements OnClickListener {
John Reckb3417f02011-01-14 11:01:05 -080041 private static final int DIVIDER_PADDING = 12; // dips
Michael Kolb370a4f32010-10-06 10:45:32 -070042
43 interface Controller {
44 public void onTop(int level, Object data);
45 }
46
47 private ImageButton mBackButton;
48 private Controller mController;
49 private List<Crumb> mCrumbs;
50 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040051 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080052 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080053 private int mMaxVisible = -1;
Michael Kolb370a4f32010-10-06 10:45:32 -070054
55 /**
56 * @param context
57 * @param attrs
58 * @param defStyle
59 */
60 public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
61 super(context, attrs, defStyle);
62 init(context);
63 }
64
65 /**
66 * @param context
67 * @param attrs
68 */
69 public BreadCrumbView(Context context, AttributeSet attrs) {
70 super(context, attrs);
71 init(context);
72 }
73
74 /**
75 * @param context
76 */
77 public BreadCrumbView(Context context) {
78 super(context);
79 init(context);
80 }
81
82 private void init(Context ctx) {
83 mUseBackButton = false;
84 mCrumbs = new ArrayList<Crumb>();
John Reckb3417f02011-01-14 11:01:05 -080085 TypedArray a = ctx.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
86 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
87 a.recycle();
88 mDividerPadding = DIVIDER_PADDING * ctx.getResources().getDisplayMetrics().density;
John Reck89f73c12010-12-01 10:10:14 -080089 addBackButton();
Michael Kolb370a4f32010-10-06 10:45:32 -070090 }
91
92 public void setUseBackButton(boolean useflag) {
93 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -080094 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -070095 }
96
97 public void setController(Controller ctl) {
98 mController = ctl;
99 }
100
John Reck89f73c12010-12-01 10:10:14 -0800101 public int getMaxVisible() {
102 return mMaxVisible;
103 }
104
105 public void setMaxVisible(int max) {
106 mMaxVisible = max;
107 updateVisible();
108 }
109
110 public int getTopLevel() {
111 return mCrumbs.size();
112 }
113
Michael Kolb370a4f32010-10-06 10:45:32 -0700114 public Object getTopData() {
115 Crumb c = getTopCrumb();
116 if (c != null) {
117 return c.data;
118 }
119 return null;
120 }
121
122 public int size() {
123 return mCrumbs.size();
124 }
125
126 public void clear() {
127 while (mCrumbs.size() > 1) {
128 pop(false);
129 }
130 pop(true);
131 }
132
133 public void notifyController() {
134 if (mController != null) {
135 if (mCrumbs.size() > 0) {
136 mController.onTop(mCrumbs.size(), getTopCrumb().data);
137 } else {
138 mController.onTop(0, null);
139 }
140 }
141 }
142
Leon Scroggins905250c2010-12-17 15:25:33 -0500143 public View pushView(String name, Object data) {
144 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400145 }
146
Leon Scroggins905250c2010-12-17 15:25:33 -0500147 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400148 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700149 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500150 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700151 }
152
153 public void pushView(View view, Object data) {
154 Crumb crumb = new Crumb(view, true, data);
155 pushCrumb(crumb);
156 }
157
158 public void popView() {
159 pop(true);
160 }
161
162 private void addBackButton() {
163 mBackButton = new ImageButton(mContext);
164 mBackButton.setImageResource(R.drawable.ic_back_normal);
165 mBackButton.setBackgroundResource(R.drawable.browserbarbutton);
166 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
167 LayoutParams.MATCH_PARENT));
168 mBackButton.setOnClickListener(this);
Leon Scroggins905250c2010-12-17 15:25:33 -0500169 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700170 addView(mBackButton, 0);
171 }
172
173 private void pushCrumb(Crumb crumb) {
John Reck89f73c12010-12-01 10:10:14 -0800174 if (mCrumbs.size() > 0) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700175 addSeparator();
176 }
177 mCrumbs.add(crumb);
178 addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800179 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700180 crumb.crumbView.setOnClickListener(this);
181 }
182
183 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800184 View sep = makeDividerView();
185 sep.setLayoutParams(makeDividerLayoutParams());
Michael Kolb370a4f32010-10-06 10:45:32 -0700186 addView(sep);
187 }
188
John Reckb3417f02011-01-14 11:01:05 -0800189 private ImageView makeDividerView() {
190 ImageView result = new ImageView(mContext);
191 result.setImageDrawable(mSeparatorDrawable);
192 result.setScaleType(ImageView.ScaleType.FIT_XY);
193 return result;
194 }
195
196 private LayoutParams makeDividerLayoutParams() {
197 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
198 LayoutParams.MATCH_PARENT);
199 params.topMargin = (int) mDividerPadding;
200 params.bottomMargin = (int) mDividerPadding;
201 return params;
202 }
203
Michael Kolb370a4f32010-10-06 10:45:32 -0700204 private void pop(boolean notify) {
205 int n = mCrumbs.size();
206 if (n > 0) {
207 removeLastView();
208 if (!mUseBackButton || (n > 1)) {
209 // remove separator
210 removeLastView();
211 }
212 mCrumbs.remove(n - 1);
213 if (mUseBackButton) {
214 Crumb top = getTopCrumb();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400215 if (top != null && top.canGoBack) {
216 mBackButton.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700217 } else {
Leon Scroggins905250c2010-12-17 15:25:33 -0500218 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700219 }
220 }
John Reck89f73c12010-12-01 10:10:14 -0800221 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700222 if (notify) {
223 notifyController();
224 }
225 }
226 }
227
John Reck89f73c12010-12-01 10:10:14 -0800228 private void updateVisible() {
229 // start at index 1 (0 == back button)
230 int childIndex = 1;
231 if (mMaxVisible >= 0) {
232 int invisibleCrumbs = size() - mMaxVisible;
233 if (invisibleCrumbs > 0) {
234 int crumbIndex = 0;
235 while (crumbIndex < invisibleCrumbs) {
236 // Set the crumb to GONE.
237 getChildAt(childIndex).setVisibility(View.GONE);
238 childIndex++;
239 // Each crumb is followed by a separator (except the last
240 // one). Also make it GONE
241 if (getChildAt(childIndex) != null) {
242 getChildAt(childIndex).setVisibility(View.GONE);
243 }
244 childIndex++;
245 // Move to the next crumb.
246 crumbIndex++;
247 }
248 }
249 // Make sure the last two are visible.
250 int childCount = getChildCount();
251 while (childIndex < childCount) {
252 getChildAt(childIndex).setVisibility(View.VISIBLE);
253 childIndex++;
254 }
255 } else {
256 int count = getChildCount();
257 for (int i = childIndex; i < count ; i++) {
258 getChildAt(i).setVisibility(View.VISIBLE);
259 }
260 }
261 if (mUseBackButton) {
262 boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
Leon Scroggins905250c2010-12-17 15:25:33 -0500263 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800264 } else {
265 mBackButton.setVisibility(View.GONE);
266 }
267 }
268
Michael Kolb370a4f32010-10-06 10:45:32 -0700269 private void removeLastView() {
270 int ix = getChildCount();
271 if (ix > 0) {
272 removeViewAt(ix-1);
273 }
274 }
275
John Reckd4893b02010-12-07 17:38:34 -0800276 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700277 Crumb crumb = null;
278 if (mCrumbs.size() > 0) {
279 crumb = mCrumbs.get(mCrumbs.size() - 1);
280 }
281 return crumb;
282 }
283
284 @Override
285 public void onClick(View v) {
286 if (mBackButton == v) {
287 popView();
288 notifyController();
289 } else {
290 // pop until view matches crumb view
291 while (v != getTopCrumb().crumbView) {
292 pop(false);
293 }
294 notifyController();
295 }
296 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400297 @Override
298 public int getBaseline() {
299 int ix = getChildCount();
300 if (ix > 0) {
301 // If there is at least one crumb, the baseline will be its
302 // baseline.
303 return getChildAt(ix-1).getBaseline();
304 }
305 return super.getBaseline();
306 }
307
308 @Override
309 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
310 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
311 int height = mSeparatorDrawable.getIntrinsicHeight();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800312 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400313 // This should only be an issue if there are currently no separators
314 // showing; i.e. if there is one crumb and no back button.
315 int mode = View.MeasureSpec.getMode(heightMeasureSpec);
316 switch(mode) {
317 case View.MeasureSpec.AT_MOST:
318 if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
319 return;
320 }
321 break;
322 case View.MeasureSpec.EXACTLY:
323 return;
324 default:
325 break;
326 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800327 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400328 }
329 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700330
331 class Crumb {
332
333 public View crumbView;
334 public boolean canGoBack;
335 public Object data;
336
337 public Crumb(String title, boolean backEnabled, Object tag) {
338 init(makeCrumbView(title), backEnabled, tag);
339 }
340
341 public Crumb(View view, boolean backEnabled, Object tag) {
342 init(view, backEnabled, tag);
343 }
344
345 private void init(View view, boolean back, Object tag) {
346 canGoBack = back;
347 crumbView = view;
348 data = tag;
349 }
350
351 private TextView makeCrumbView(String name) {
352 TextView tv = new TextView(mContext);
353 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
354 tv.setPadding(16, 0, 16, 0);
355 tv.setGravity(Gravity.CENTER_VERTICAL);
356 tv.setText(name);
357 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
358 LayoutParams.MATCH_PARENT));
Leon Scroggins74dbe012010-10-15 10:54:27 -0400359 tv.setMaxWidth(mContext.getResources().getInteger(
360 R.integer.max_width_crumb));
361 tv.setMaxLines(1);
362 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700363 return tv;
364 }
365
366 }
367
368}