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