blob: 7775f00a50a47ee7e1068823c4b72f04a0304837 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.view.menu;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.util.ArrayList;
/**
* @hide
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
// TODO Theme/style this.
private static final int DIVIDER_PADDING = 12; // dips
private MenuBuilder mMenu;
private int mMaxItems;
private int mWidthLimit;
private boolean mReserveOverflow;
private OverflowMenuButton mOverflowButton;
private MenuPopupHelper mOverflowPopup;
private float mDividerPadding;
private Drawable mDivider;
private final Runnable mShowOverflow = new Runnable() {
public void run() {
showOverflowMenu();
}
};
private class OpenOverflowRunnable implements Runnable {
private MenuPopupHelper mPopup;
public OpenOverflowRunnable(MenuPopupHelper popup) {
mPopup = popup;
}
public void run() {
if (mPopup.tryShow()) {
mOverflowPopup = mPopup;
mPostedOpenRunnable = null;
}
}
}
private OpenOverflowRunnable mPostedOpenRunnable;
public ActionMenuView(Context context) {
this(context, null);
}
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
// Measure for initial configuration
mMaxItems = getMaxActionButtons();
// TODO There has to be a better way to indicate that we don't have a hard menu key.
final int screen = res.getConfiguration().screenLayout;
mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
Configuration.SCREENLAYOUT_SIZE_XLARGE;
mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
a.recycle();
mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
setBaselineAligned(false);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final int screen = newConfig.screenLayout;
mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
Configuration.SCREENLAYOUT_SIZE_XLARGE;
mMaxItems = getMaxActionButtons();
mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
if (mMenu != null) {
mMenu.setMaxActionItems(mMaxItems);
updateChildren(false);
}
if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
mOverflowPopup.dismiss();
post(mShowOverflow);
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
mOverflowPopup.dismiss();
}
removeCallbacks(mShowOverflow);
if (mPostedOpenRunnable != null) {
removeCallbacks(mPostedOpenRunnable);
}
}
private int getMaxActionButtons() {
return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
}
public boolean isOverflowReserved() {
return mReserveOverflow;
}
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
public View getOverflowButton() {
return mOverflowButton;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_VERTICAL;
return params;
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p instanceof LayoutParams) {
LayoutParams result = new LayoutParams((LayoutParams) p);
if (result.gravity <= Gravity.NO_GRAVITY) {
result.gravity = Gravity.CENTER_VERTICAL;
}
return result;
}
return generateDefaultLayoutParams();
}
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
public int getWindowAnimations() {
return 0;
}
public void initialize(MenuBuilder menu, int menuType) {
int width = mWidthLimit;
if (mReserveOverflow) {
if (mOverflowButton == null) {
OverflowMenuButton button = new OverflowMenuButton(mContext);
mOverflowButton = button;
}
final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mOverflowButton.measure(spec, spec);
width -= mOverflowButton.getMeasuredWidth();
}
menu.setActionWidthLimit(width);
menu.setMaxActionItems(mMaxItems);
final boolean cleared = mMenu != menu;
mMenu = menu;
updateChildren(cleared);
}
public void updateChildren(boolean cleared) {
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
final int itemCount = itemsToShow.size();
boolean needsDivider = false;
int childIndex = 0;
for (int i = 0; i < itemCount; i++) {
final MenuItemImpl itemData = itemsToShow.get(i);
boolean hasDivider = false;
if (needsDivider) {
if (!isDivider(getChildAt(childIndex))) {
addView(makeDividerView(), childIndex, makeDividerLayoutParams());
}
hasDivider = true;
childIndex++;
}
View childToAdd = itemData.getActionView();
boolean needsPreDivider = false;
if (childToAdd != null) {
childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
} else {
ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
MenuBuilder.TYPE_ACTION_BUTTON, this);
view.setItemInvoker(this);
needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
itemData.getIcon() == null;
needsDivider = view.hasText();
childToAdd = view;
}
boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
if (needsPreDivider) childIndex++;
if (getChildAt(childIndex) != childToAdd) {
addView(childToAdd, childIndex);
}
childIndex++;
}
final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
if (hasOverflow != needsOverflow) {
if (needsOverflow) {
if (mOverflowButton == null) {
OverflowMenuButton button = new OverflowMenuButton(mContext);
mOverflowButton = button;
}
boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
if (addDivider && itemCount > 0) {
addView(makeDividerView(), childIndex, makeDividerLayoutParams());
childIndex++;
}
addView(mOverflowButton, childIndex);
childIndex++;
} else {
removeView(mOverflowButton);
}
} else {
if (needsOverflow) {
boolean overflowDivider = itemCount > 0;
boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
overflowDivider);
if (addDivider && itemCount > 0) {
addView(makeDividerView(), childIndex, makeDividerLayoutParams());
}
if (overflowDivider) {
childIndex += 2;
} else {
childIndex++;
}
}
}
while (getChildCount() > childIndex) {
removeViewAt(childIndex);
}
}
private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
final int childCount = getChildCount();
boolean found = false;
for (int i = start; i < childCount; i++) {
final View child = getChildAt(i);
if (child == targetChild) {
found = true;
break;
}
}
if (!found) {
return needsPreDivider;
}
for (int i = start; i < getChildCount(); ) {
final View child = getChildAt(i);
if (needsPreDivider && isDivider(child)) {
needsPreDivider = false;
i++;
continue;
}
if (child == targetChild) break;
removeViewAt(i);
}
return needsPreDivider;
}
private static boolean isDivider(View v) {
return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
}
public boolean showOverflowMenu() {
if (mOverflowButton != null && !isOverflowMenuShowing()) {
mMenu.getCallback().onMenuModeChange(mMenu);
return true;
}
return false;
}
public void openOverflowMenu() {
OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
mPostedOpenRunnable = new OpenOverflowRunnable(popup);
// Post this for later; we might still need a layout for the anchor to be right.
post(mPostedOpenRunnable);
}
public boolean isOverflowMenuShowing() {
return mOverflowPopup != null && mOverflowPopup.isShowing();
}
public boolean isOverflowMenuOpen() {
return mOverflowPopup != null;
}
public boolean hideOverflowMenu() {
if (mPostedOpenRunnable != null) {
removeCallbacks(mPostedOpenRunnable);
return true;
}
MenuPopupHelper popup = mOverflowPopup;
if (popup != null) {
popup.dismiss();
return true;
}
return false;
}
private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
view.setItemInvoker(this);
boolean hasText = view.hasText();
if (hasText && needsDivider) {
addView(makeDividerView(), makeDividerLayoutParams());
}
addView(view);
return hasText;
}
private ImageView makeDividerView() {
ImageView result = new ImageView(mContext);
result.setImageDrawable(mDivider);
result.setScaleType(ImageView.ScaleType.FIT_XY);
result.setId(com.android.internal.R.id.action_menu_divider);
return result;
}
private LayoutParams makeDividerLayoutParams() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
params.topMargin = (int) mDividerPadding;
params.bottomMargin = (int) mDividerPadding;
return params;
}
private LayoutParams makeActionViewLayoutParams(View view) {
return generateLayoutParams(view.getLayoutParams());
}
private class OverflowMenuButton extends ImageButton {
public OverflowMenuButton(Context context) {
super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
setClickable(true);
setFocusable(true);
setVisibility(VISIBLE);
setEnabled(true);
}
@Override
public boolean performClick() {
if (super.performClick()) {
return true;
}
playSoundEffect(SoundEffectConstants.CLICK);
showOverflowMenu();
return true;
}
}
private class OverflowPopup extends MenuPopupHelper {
public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly) {
super(context, menu, anchorView, overflowOnly);
}
@Override
public void onDismiss() {
super.onDismiss();
mMenu.getCallback().onCloseMenu(mMenu, true);
mOverflowPopup = null;
}
}
}