|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.menu; |
|
6 |
|
7 import org.mozilla.gecko.AppConstants; |
|
8 import org.mozilla.gecko.R; |
|
9 import org.mozilla.gecko.util.ThreadUtils; |
|
10 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior; |
|
11 import org.mozilla.gecko.widget.GeckoActionProvider; |
|
12 |
|
13 import android.content.ComponentName; |
|
14 import android.content.Context; |
|
15 import android.content.Intent; |
|
16 import android.util.AttributeSet; |
|
17 import android.util.Log; |
|
18 import android.view.ActionProvider; |
|
19 import android.view.KeyEvent; |
|
20 import android.view.LayoutInflater; |
|
21 import android.view.Menu; |
|
22 import android.view.MenuItem; |
|
23 import android.view.SubMenu; |
|
24 import android.view.View; |
|
25 import android.view.ViewGroup; |
|
26 import android.widget.AdapterView; |
|
27 import android.widget.BaseAdapter; |
|
28 import android.widget.LinearLayout; |
|
29 import android.widget.ListView; |
|
30 |
|
31 import java.util.ArrayList; |
|
32 import java.util.HashMap; |
|
33 import java.util.List; |
|
34 import java.util.Map; |
|
35 |
|
36 public class GeckoMenu extends ListView |
|
37 implements Menu, |
|
38 AdapterView.OnItemClickListener, |
|
39 GeckoMenuItem.OnShowAsActionChangedListener { |
|
40 private static final String LOGTAG = "GeckoMenu"; |
|
41 |
|
42 /** |
|
43 * Controls whether off-UI-thread method calls in this class cause an |
|
44 * exception or just logging. |
|
45 */ |
|
46 private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_BUILD ? AssertBehavior.NONE : AssertBehavior.THROW; |
|
47 |
|
48 /* |
|
49 * A callback for a menu item selected event. |
|
50 */ |
|
51 public static interface Callback { |
|
52 // Called when a menu item is selected, with the actual menu item as the argument. |
|
53 public boolean onMenuItemSelected(MenuItem item); |
|
54 } |
|
55 |
|
56 /* |
|
57 * An interface for a presenter to show the menu. |
|
58 * Either an Activity or a View can be a presenter, that can watch for events |
|
59 * and show/hide menu. |
|
60 */ |
|
61 public static interface MenuPresenter { |
|
62 // Open the menu. |
|
63 public void openMenu(); |
|
64 |
|
65 // Show the actual view containing the menu items. This can either be a parent or sub-menu. |
|
66 public void showMenu(View menu); |
|
67 |
|
68 // Close the menu. |
|
69 public void closeMenu(); |
|
70 } |
|
71 |
|
72 /* |
|
73 * An interface for a presenter of action-items. |
|
74 * Either an Activity or a View can be a presenter, that can watch for events |
|
75 * and add/remove action-items. If not ActionItemBarPresenter, the menu uses a |
|
76 * DefaultActionItemBar, that shows the action-items as a header over list-view. |
|
77 */ |
|
78 public static interface ActionItemBarPresenter { |
|
79 // Add an action-item. |
|
80 public boolean addActionItem(View actionItem); |
|
81 |
|
82 // Remove an action-item. |
|
83 public void removeActionItem(View actionItem); |
|
84 } |
|
85 |
|
86 protected static final int NO_ID = 0; |
|
87 |
|
88 // List of all menu items. |
|
89 private List<GeckoMenuItem> mItems; |
|
90 |
|
91 // Map of "always" action-items in action-bar and their views. |
|
92 private Map<GeckoMenuItem, View> mPrimaryActionItems; |
|
93 |
|
94 // Map of "ifRoom" action-items in action-bar and their views. |
|
95 private Map<GeckoMenuItem, View> mSecondaryActionItems; |
|
96 |
|
97 // Reference to a callback for menu events. |
|
98 private Callback mCallback; |
|
99 |
|
100 // Reference to menu presenter. |
|
101 private MenuPresenter mMenuPresenter; |
|
102 |
|
103 // Reference to "always" action-items bar in action-bar. |
|
104 private ActionItemBarPresenter mPrimaryActionItemBar; |
|
105 |
|
106 // Reference to "ifRoom" action-items bar in action-bar. |
|
107 private final ActionItemBarPresenter mSecondaryActionItemBar; |
|
108 |
|
109 // Adapter to hold the list of menu items. |
|
110 private MenuItemsAdapter mAdapter; |
|
111 |
|
112 // Show/hide icons in the list. |
|
113 private boolean mShowIcons; |
|
114 |
|
115 public GeckoMenu(Context context) { |
|
116 this(context, null); |
|
117 } |
|
118 |
|
119 public GeckoMenu(Context context, AttributeSet attrs) { |
|
120 this(context, attrs, R.attr.geckoMenuListViewStyle); |
|
121 } |
|
122 |
|
123 public GeckoMenu(Context context, AttributeSet attrs, int defStyle) { |
|
124 super(context, attrs, defStyle); |
|
125 |
|
126 setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, |
|
127 LayoutParams.FILL_PARENT)); |
|
128 |
|
129 // Attach an adapter. |
|
130 mAdapter = new MenuItemsAdapter(); |
|
131 setAdapter(mAdapter); |
|
132 setOnItemClickListener(this); |
|
133 |
|
134 mShowIcons = false; |
|
135 mItems = new ArrayList<GeckoMenuItem>(); |
|
136 mPrimaryActionItems = new HashMap<GeckoMenuItem, View>(); |
|
137 mSecondaryActionItems = new HashMap<GeckoMenuItem, View>(); |
|
138 |
|
139 mPrimaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null); |
|
140 mSecondaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null); |
|
141 } |
|
142 |
|
143 private static void assertOnUiThread() { |
|
144 ThreadUtils.assertOnUiThread(THREAD_ASSERT_BEHAVIOR); |
|
145 } |
|
146 |
|
147 @Override |
|
148 public MenuItem add(CharSequence title) { |
|
149 GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title); |
|
150 addItem(menuItem); |
|
151 return menuItem; |
|
152 } |
|
153 |
|
154 @Override |
|
155 public MenuItem add(int groupId, int itemId, int order, int titleRes) { |
|
156 GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, titleRes); |
|
157 addItem(menuItem); |
|
158 return menuItem; |
|
159 } |
|
160 |
|
161 @Override |
|
162 public MenuItem add(int titleRes) { |
|
163 GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, titleRes); |
|
164 addItem(menuItem); |
|
165 return menuItem; |
|
166 } |
|
167 |
|
168 @Override |
|
169 public MenuItem add(int groupId, int itemId, int order, CharSequence title) { |
|
170 GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, title); |
|
171 addItem(menuItem); |
|
172 return menuItem; |
|
173 } |
|
174 |
|
175 private void addItem(GeckoMenuItem menuItem) { |
|
176 assertOnUiThread(); |
|
177 menuItem.setOnShowAsActionChangedListener(this); |
|
178 mAdapter.addMenuItem(menuItem); |
|
179 mItems.add(menuItem); |
|
180 } |
|
181 |
|
182 private boolean addActionItem(final GeckoMenuItem menuItem) { |
|
183 assertOnUiThread(); |
|
184 menuItem.setOnShowAsActionChangedListener(this); |
|
185 |
|
186 final View actionView = menuItem.getActionView(); |
|
187 final int actionEnum = menuItem.getActionEnum(); |
|
188 boolean added = false; |
|
189 |
|
190 if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) { |
|
191 if (mPrimaryActionItems.size() == 0 && |
|
192 mPrimaryActionItemBar instanceof DefaultActionItemBar) { |
|
193 // Reset the adapter before adding the header view to a list. |
|
194 setAdapter(null); |
|
195 addHeaderView((DefaultActionItemBar) mPrimaryActionItemBar); |
|
196 setAdapter(mAdapter); |
|
197 } |
|
198 |
|
199 if (added = mPrimaryActionItemBar.addActionItem(actionView)) { |
|
200 mPrimaryActionItems.put(menuItem, actionView); |
|
201 mItems.add(menuItem); |
|
202 } |
|
203 } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) { |
|
204 if (mSecondaryActionItems.size() == 0) { |
|
205 // Reset the adapter before adding the header view to a list. |
|
206 setAdapter(null); |
|
207 addHeaderView((DefaultActionItemBar) mSecondaryActionItemBar); |
|
208 setAdapter(mAdapter); |
|
209 } |
|
210 |
|
211 if (added = mSecondaryActionItemBar.addActionItem(actionView)) { |
|
212 mSecondaryActionItems.put(menuItem, actionView); |
|
213 mItems.add(menuItem); |
|
214 } |
|
215 } |
|
216 |
|
217 // Set the listeners. |
|
218 if (actionView instanceof MenuItemActionBar) { |
|
219 ((MenuItemActionBar) actionView).setOnClickListener(new View.OnClickListener() { |
|
220 @Override |
|
221 public void onClick(View view) { |
|
222 handleMenuItemClick(menuItem); |
|
223 } |
|
224 }); |
|
225 } else if (actionView instanceof MenuItemActionView) { |
|
226 ((MenuItemActionView) actionView).setMenuItemClickListener(new View.OnClickListener() { |
|
227 @Override |
|
228 public void onClick(View view) { |
|
229 handleMenuItemClick(menuItem); |
|
230 } |
|
231 }); |
|
232 } |
|
233 |
|
234 return added; |
|
235 } |
|
236 |
|
237 @Override |
|
238 public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) { |
|
239 return 0; |
|
240 } |
|
241 |
|
242 @Override |
|
243 public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) { |
|
244 MenuItem menuItem = add(groupId, itemId, order, title); |
|
245 return addSubMenu(menuItem); |
|
246 } |
|
247 |
|
248 @Override |
|
249 public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) { |
|
250 MenuItem menuItem = add(groupId, itemId, order, titleRes); |
|
251 return addSubMenu(menuItem); |
|
252 } |
|
253 |
|
254 @Override |
|
255 public SubMenu addSubMenu(CharSequence title) { |
|
256 MenuItem menuItem = add(title); |
|
257 return addSubMenu(menuItem); |
|
258 } |
|
259 |
|
260 @Override |
|
261 public SubMenu addSubMenu(int titleRes) { |
|
262 MenuItem menuItem = add(titleRes); |
|
263 return addSubMenu(menuItem); |
|
264 } |
|
265 |
|
266 private SubMenu addSubMenu(MenuItem menuItem) { |
|
267 GeckoSubMenu subMenu = new GeckoSubMenu(getContext()); |
|
268 subMenu.setMenuItem(menuItem); |
|
269 subMenu.setCallback(mCallback); |
|
270 subMenu.setMenuPresenter(mMenuPresenter); |
|
271 ((GeckoMenuItem) menuItem).setSubMenu(subMenu); |
|
272 return subMenu; |
|
273 } |
|
274 |
|
275 private void removePrimaryActionBarView() { |
|
276 // Reset the adapter before removing the header view from a list. |
|
277 setAdapter(null); |
|
278 removeHeaderView((DefaultActionItemBar) mPrimaryActionItemBar); |
|
279 setAdapter(mAdapter); |
|
280 } |
|
281 |
|
282 private void removeSecondaryActionBarView() { |
|
283 // Reset the adapter before removing the header view from a list. |
|
284 setAdapter(null); |
|
285 removeHeaderView((DefaultActionItemBar) mSecondaryActionItemBar); |
|
286 setAdapter(mAdapter); |
|
287 } |
|
288 |
|
289 @Override |
|
290 public void clear() { |
|
291 assertOnUiThread(); |
|
292 for (GeckoMenuItem menuItem : mItems) { |
|
293 if (menuItem.hasSubMenu()) { |
|
294 SubMenu sub = menuItem.getSubMenu(); |
|
295 if (sub == null) { |
|
296 continue; |
|
297 } |
|
298 try { |
|
299 sub.clear(); |
|
300 } catch (Exception ex) { |
|
301 Log.e(LOGTAG, "Couldn't clear submenu.", ex); |
|
302 } |
|
303 } |
|
304 } |
|
305 |
|
306 mAdapter.clear(); |
|
307 mItems.clear(); |
|
308 |
|
309 /* |
|
310 * Reinflating the menu will re-add any action items to the toolbar, so |
|
311 * remove the old ones. This also ensures that any text associated with |
|
312 * these is switched to the correct locale. |
|
313 */ |
|
314 if (mPrimaryActionItemBar != null) { |
|
315 for (View item : mPrimaryActionItems.values()) { |
|
316 mPrimaryActionItemBar.removeActionItem(item); |
|
317 } |
|
318 } |
|
319 mPrimaryActionItems.clear(); |
|
320 |
|
321 if (mSecondaryActionItemBar != null) { |
|
322 for (View item : mSecondaryActionItems.values()) { |
|
323 mSecondaryActionItemBar.removeActionItem(item); |
|
324 } |
|
325 } |
|
326 mSecondaryActionItems.clear(); |
|
327 |
|
328 // Remove the view, too -- the first addActionItem will re-add it, |
|
329 // and this is simpler than changing that logic. |
|
330 if (mPrimaryActionItemBar instanceof DefaultActionItemBar) { |
|
331 removePrimaryActionBarView(); |
|
332 } |
|
333 |
|
334 removeSecondaryActionBarView(); |
|
335 } |
|
336 |
|
337 @Override |
|
338 public void close() { |
|
339 if (mMenuPresenter != null) |
|
340 mMenuPresenter.closeMenu(); |
|
341 } |
|
342 |
|
343 private void showMenu(View viewForMenu) { |
|
344 if (mMenuPresenter != null) |
|
345 mMenuPresenter.showMenu(viewForMenu); |
|
346 } |
|
347 |
|
348 @Override |
|
349 public MenuItem findItem(int id) { |
|
350 for (GeckoMenuItem menuItem : mItems) { |
|
351 if (menuItem.getItemId() == id) { |
|
352 return menuItem; |
|
353 } else if (menuItem.hasSubMenu()) { |
|
354 if (!menuItem.hasActionProvider()) { |
|
355 SubMenu subMenu = menuItem.getSubMenu(); |
|
356 MenuItem item = subMenu.findItem(id); |
|
357 if (item != null) |
|
358 return item; |
|
359 } |
|
360 } |
|
361 } |
|
362 return null; |
|
363 } |
|
364 |
|
365 @Override |
|
366 public MenuItem getItem(int index) { |
|
367 if (index < mItems.size()) |
|
368 return mItems.get(index); |
|
369 |
|
370 return null; |
|
371 } |
|
372 |
|
373 @Override |
|
374 public boolean hasVisibleItems() { |
|
375 assertOnUiThread(); |
|
376 for (GeckoMenuItem menuItem : mItems) { |
|
377 if (menuItem.isVisible() && |
|
378 !mPrimaryActionItems.containsKey(menuItem) && |
|
379 !mSecondaryActionItems.containsKey(menuItem)) |
|
380 return true; |
|
381 } |
|
382 |
|
383 return false; |
|
384 } |
|
385 |
|
386 @Override |
|
387 public boolean isShortcutKey(int keyCode, KeyEvent event) { |
|
388 return true; |
|
389 } |
|
390 |
|
391 @Override |
|
392 public boolean performIdentifierAction(int id, int flags) { |
|
393 return false; |
|
394 } |
|
395 |
|
396 @Override |
|
397 public boolean performShortcut(int keyCode, KeyEvent event, int flags) { |
|
398 return false; |
|
399 } |
|
400 |
|
401 @Override |
|
402 public void removeGroup(int groupId) { |
|
403 } |
|
404 |
|
405 @Override |
|
406 public void removeItem(int id) { |
|
407 assertOnUiThread(); |
|
408 GeckoMenuItem item = (GeckoMenuItem) findItem(id); |
|
409 if (item == null) |
|
410 return; |
|
411 |
|
412 // Remove it from any sub-menu. |
|
413 for (GeckoMenuItem menuItem : mItems) { |
|
414 if (menuItem.hasSubMenu()) { |
|
415 SubMenu subMenu = menuItem.getSubMenu(); |
|
416 if (subMenu != null && subMenu.findItem(id) != null) { |
|
417 subMenu.removeItem(id); |
|
418 return; |
|
419 } |
|
420 } |
|
421 } |
|
422 |
|
423 // Remove it from own menu. |
|
424 if (mPrimaryActionItems.containsKey(item)) { |
|
425 if (mPrimaryActionItemBar != null) |
|
426 mPrimaryActionItemBar.removeActionItem(mPrimaryActionItems.get(item)); |
|
427 |
|
428 mPrimaryActionItems.remove(item); |
|
429 mItems.remove(item); |
|
430 |
|
431 if (mPrimaryActionItems.size() == 0 && |
|
432 mPrimaryActionItemBar instanceof DefaultActionItemBar) { |
|
433 removePrimaryActionBarView(); |
|
434 } |
|
435 |
|
436 return; |
|
437 } |
|
438 |
|
439 if (mSecondaryActionItems.containsKey(item)) { |
|
440 if (mSecondaryActionItemBar != null) |
|
441 mSecondaryActionItemBar.removeActionItem(mSecondaryActionItems.get(item)); |
|
442 |
|
443 mSecondaryActionItems.remove(item); |
|
444 mItems.remove(item); |
|
445 |
|
446 if (mSecondaryActionItems.size() == 0) { |
|
447 removeSecondaryActionBarView(); |
|
448 } |
|
449 |
|
450 return; |
|
451 } |
|
452 |
|
453 mAdapter.removeMenuItem(item); |
|
454 mItems.remove(item); |
|
455 } |
|
456 |
|
457 @Override |
|
458 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) { |
|
459 } |
|
460 |
|
461 @Override |
|
462 public void setGroupEnabled(int group, boolean enabled) { |
|
463 } |
|
464 |
|
465 @Override |
|
466 public void setGroupVisible(int group, boolean visible) { |
|
467 } |
|
468 |
|
469 @Override |
|
470 public void setQwertyMode(boolean isQwerty) { |
|
471 } |
|
472 |
|
473 @Override |
|
474 public int size() { |
|
475 return mItems.size(); |
|
476 } |
|
477 |
|
478 @Override |
|
479 public boolean hasActionItemBar() { |
|
480 return (mPrimaryActionItemBar != null) && (mSecondaryActionItemBar != null); |
|
481 } |
|
482 |
|
483 @Override |
|
484 public void onShowAsActionChanged(GeckoMenuItem item) { |
|
485 removeItem(item.getItemId()); |
|
486 |
|
487 if (item.isActionItem() && addActionItem(item)) { |
|
488 return; |
|
489 } |
|
490 |
|
491 addItem(item); |
|
492 } |
|
493 |
|
494 public void onItemChanged(GeckoMenuItem item) { |
|
495 assertOnUiThread(); |
|
496 if (item.isActionItem()) { |
|
497 final View actionView; |
|
498 if (item.getActionEnum() == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) { |
|
499 actionView = mPrimaryActionItems.get(item); |
|
500 } else { |
|
501 actionView = mSecondaryActionItems.get(item); |
|
502 } |
|
503 |
|
504 if (actionView != null) { |
|
505 // The update could be coming from the background thread. |
|
506 // Post a runnable on the UI thread of the view for it to update. |
|
507 final GeckoMenuItem menuItem = item; |
|
508 actionView.post(new Runnable() { |
|
509 @Override |
|
510 public void run() { |
|
511 if (menuItem.isVisible()) { |
|
512 actionView.setVisibility(View.VISIBLE); |
|
513 if (actionView instanceof MenuItemActionBar) { |
|
514 ((MenuItemActionBar) actionView).initialize(menuItem); |
|
515 } else { |
|
516 ((MenuItemActionView) actionView).initialize(menuItem); |
|
517 } |
|
518 } else { |
|
519 actionView.setVisibility(View.GONE); |
|
520 } |
|
521 } |
|
522 }); |
|
523 } |
|
524 } else { |
|
525 mAdapter.notifyDataSetChanged(); |
|
526 } |
|
527 } |
|
528 |
|
529 @Override |
|
530 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
|
531 // We might be showing headers. Account them while using the position. |
|
532 position -= getHeaderViewsCount(); |
|
533 |
|
534 GeckoMenuItem item = mAdapter.getItem(position); |
|
535 handleMenuItemClick(item); |
|
536 } |
|
537 |
|
538 private void handleMenuItemClick(GeckoMenuItem item) { |
|
539 if (!item.isEnabled()) |
|
540 return; |
|
541 |
|
542 if (item.invoke()) { |
|
543 close(); |
|
544 } else if (item.hasSubMenu()) { |
|
545 // Refresh the submenu for the provider. |
|
546 GeckoActionProvider provider = item.getGeckoActionProvider(); |
|
547 if (provider != null) { |
|
548 GeckoSubMenu subMenu = new GeckoSubMenu(getContext()); |
|
549 subMenu.setShowIcons(true); |
|
550 provider.onPrepareSubMenu(subMenu); |
|
551 item.setSubMenu(subMenu); |
|
552 } |
|
553 |
|
554 // Show the submenu. |
|
555 GeckoSubMenu subMenu = (GeckoSubMenu) item.getSubMenu(); |
|
556 showMenu(subMenu); |
|
557 } else { |
|
558 close(); |
|
559 mCallback.onMenuItemSelected(item); |
|
560 } |
|
561 } |
|
562 |
|
563 public Callback getCallback() { |
|
564 return mCallback; |
|
565 } |
|
566 |
|
567 public MenuPresenter getMenuPresenter() { |
|
568 return mMenuPresenter; |
|
569 } |
|
570 |
|
571 public void setCallback(Callback callback) { |
|
572 mCallback = callback; |
|
573 |
|
574 // Update the submenus just in case this changes on the fly. |
|
575 for (GeckoMenuItem menuItem : mItems) { |
|
576 if (menuItem.hasSubMenu()) { |
|
577 GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu(); |
|
578 subMenu.setCallback(mCallback); |
|
579 } |
|
580 } |
|
581 } |
|
582 |
|
583 public void setMenuPresenter(MenuPresenter presenter) { |
|
584 mMenuPresenter = presenter; |
|
585 |
|
586 // Update the submenus just in case this changes on the fly. |
|
587 for (GeckoMenuItem menuItem : mItems) { |
|
588 if (menuItem.hasSubMenu()) { |
|
589 GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu(); |
|
590 subMenu.setMenuPresenter(mMenuPresenter); |
|
591 } |
|
592 } |
|
593 } |
|
594 |
|
595 public void setActionItemBarPresenter(ActionItemBarPresenter presenter) { |
|
596 mPrimaryActionItemBar = presenter; |
|
597 } |
|
598 |
|
599 public void setShowIcons(boolean show) { |
|
600 if (mShowIcons != show) { |
|
601 mShowIcons = show; |
|
602 mAdapter.notifyDataSetChanged(); |
|
603 } |
|
604 } |
|
605 |
|
606 // Action Items are added to the header view by default. |
|
607 // URL bar can register itself as a presenter, in case it has a different place to show them. |
|
608 public static class DefaultActionItemBar extends LinearLayout |
|
609 implements ActionItemBarPresenter { |
|
610 private final int mRowHeight; |
|
611 private float mWeightSum; |
|
612 |
|
613 public DefaultActionItemBar(Context context) { |
|
614 this(context, null); |
|
615 } |
|
616 |
|
617 public DefaultActionItemBar(Context context, AttributeSet attrs) { |
|
618 super(context, attrs); |
|
619 |
|
620 mRowHeight = getResources().getDimensionPixelSize(R.dimen.menu_item_row_height); |
|
621 } |
|
622 |
|
623 @Override |
|
624 public boolean addActionItem(View actionItem) { |
|
625 ViewGroup.LayoutParams actualParams = actionItem.getLayoutParams(); |
|
626 LinearLayout.LayoutParams params; |
|
627 |
|
628 if (actualParams != null) { |
|
629 params = new LinearLayout.LayoutParams(actionItem.getLayoutParams()); |
|
630 params.width = 0; |
|
631 } else { |
|
632 params = new LinearLayout.LayoutParams(0, mRowHeight); |
|
633 } |
|
634 |
|
635 if (actionItem instanceof MenuItemActionView) { |
|
636 params.weight = ((MenuItemActionView) actionItem).getChildCount(); |
|
637 } else { |
|
638 params.weight = 1.0f; |
|
639 } |
|
640 |
|
641 mWeightSum += params.weight; |
|
642 |
|
643 actionItem.setLayoutParams(params); |
|
644 addView(actionItem); |
|
645 setWeightSum(mWeightSum); |
|
646 return true; |
|
647 } |
|
648 |
|
649 @Override |
|
650 public void removeActionItem(View actionItem) { |
|
651 if (indexOfChild(actionItem) != -1) { |
|
652 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) actionItem.getLayoutParams(); |
|
653 mWeightSum -= params.weight; |
|
654 removeView(actionItem); |
|
655 } |
|
656 } |
|
657 } |
|
658 |
|
659 // Adapter to bind menu items to the list. |
|
660 private class MenuItemsAdapter extends BaseAdapter { |
|
661 private static final int VIEW_TYPE_DEFAULT = 0; |
|
662 private static final int VIEW_TYPE_ACTION_MODE = 1; |
|
663 |
|
664 private List<GeckoMenuItem> mItems; |
|
665 |
|
666 public MenuItemsAdapter() { |
|
667 mItems = new ArrayList<GeckoMenuItem>(); |
|
668 } |
|
669 |
|
670 @Override |
|
671 public int getCount() { |
|
672 if (mItems == null) |
|
673 return 0; |
|
674 |
|
675 int visibleCount = 0; |
|
676 for (GeckoMenuItem item : mItems) { |
|
677 if (item.isVisible()) |
|
678 visibleCount++; |
|
679 } |
|
680 |
|
681 return visibleCount; |
|
682 } |
|
683 |
|
684 @Override |
|
685 public GeckoMenuItem getItem(int position) { |
|
686 for (GeckoMenuItem item : mItems) { |
|
687 if (item.isVisible()) { |
|
688 position--; |
|
689 |
|
690 if (position < 0) |
|
691 return item; |
|
692 } |
|
693 } |
|
694 |
|
695 return null; |
|
696 } |
|
697 |
|
698 @Override |
|
699 public long getItemId(int position) { |
|
700 return position; |
|
701 } |
|
702 |
|
703 @Override |
|
704 public View getView(int position, View convertView, ViewGroup parent) { |
|
705 GeckoMenuItem item = getItem(position); |
|
706 GeckoMenuItem.Layout view = null; |
|
707 |
|
708 // Try to re-use the view. |
|
709 if (convertView == null && getItemViewType(position) == VIEW_TYPE_DEFAULT) { |
|
710 view = new MenuItemDefault(parent.getContext(), null); |
|
711 } else { |
|
712 view = (GeckoMenuItem.Layout) convertView; |
|
713 } |
|
714 |
|
715 if (view == null || view instanceof MenuItemActionView) { |
|
716 // Always get from the menu item. |
|
717 // This will ensure that the default activity is refreshed. |
|
718 view = (MenuItemActionView) item.getActionView(); |
|
719 |
|
720 // ListView will not perform an item click if the row has a focusable view in it. |
|
721 // Hence, forward the click event on the menu item in the action-view to the ListView. |
|
722 final View actionView = (View) view; |
|
723 final int pos = position; |
|
724 final long id = getItemId(position); |
|
725 ((MenuItemActionView) view).setMenuItemClickListener(new View.OnClickListener() { |
|
726 @Override |
|
727 public void onClick(View v) { |
|
728 GeckoMenu listView = GeckoMenu.this; |
|
729 listView.performItemClick(actionView, pos + listView.getHeaderViewsCount(), id); |
|
730 } |
|
731 }); |
|
732 } |
|
733 |
|
734 // Initialize the view. |
|
735 view.setShowIcon(mShowIcons); |
|
736 view.initialize(item); |
|
737 return (View) view; |
|
738 } |
|
739 |
|
740 @Override |
|
741 public int getItemViewType(int position) { |
|
742 return getItem(position).getGeckoActionProvider() == null ? VIEW_TYPE_DEFAULT : VIEW_TYPE_ACTION_MODE; |
|
743 } |
|
744 |
|
745 @Override |
|
746 public int getViewTypeCount() { |
|
747 return 2; |
|
748 } |
|
749 |
|
750 @Override |
|
751 public boolean hasStableIds() { |
|
752 return false; |
|
753 } |
|
754 |
|
755 @Override |
|
756 public boolean areAllItemsEnabled() { |
|
757 // Setting this to true is a workaround to fix disappearing |
|
758 // dividers in the menu (bug 963249). |
|
759 return true; |
|
760 } |
|
761 |
|
762 @Override |
|
763 public boolean isEnabled(int position) { |
|
764 return getItem(position).isEnabled(); |
|
765 } |
|
766 |
|
767 public void addMenuItem(GeckoMenuItem menuItem) { |
|
768 if (mItems.contains(menuItem)) |
|
769 return; |
|
770 |
|
771 // Insert it in proper order. |
|
772 int index = 0; |
|
773 for (GeckoMenuItem item : mItems) { |
|
774 if (item.getOrder() > menuItem.getOrder()) { |
|
775 mItems.add(index, menuItem); |
|
776 notifyDataSetChanged(); |
|
777 return; |
|
778 } else { |
|
779 index++; |
|
780 } |
|
781 } |
|
782 |
|
783 // Add the menuItem at the end. |
|
784 mItems.add(menuItem); |
|
785 notifyDataSetChanged(); |
|
786 } |
|
787 |
|
788 public void removeMenuItem(GeckoMenuItem menuItem) { |
|
789 // Remove it from the list. |
|
790 mItems.remove(menuItem); |
|
791 notifyDataSetChanged(); |
|
792 } |
|
793 |
|
794 public void clear() { |
|
795 mItems.clear(); |
|
796 notifyDataSetChanged(); |
|
797 } |
|
798 |
|
799 public GeckoMenuItem getMenuItem(int id) { |
|
800 for (GeckoMenuItem item : mItems) { |
|
801 if (item.getItemId() == id) |
|
802 return item; |
|
803 } |
|
804 |
|
805 return null; |
|
806 } |
|
807 } |
|
808 } |