blob: b505ea0e0c10a779576a851aa7f28e0af2f79f3e [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 com.android.internal.view.menu.MenuPresenter.Callback;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.widget.PopupWindow;
/**
* Presents a menu as a small, simple popup anchored to another view.
*
* @hide
*/
public class MenuPopupHelper implements PopupWindow.OnDismissListener {
private final Context mContext;
private final MenuBuilder mMenu;
private final boolean mOverflowOnly;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
private View mAnchorView;
private MenuPopup mPopup;
private int mDropDownGravity = Gravity.START;
private boolean mForceShowIcon;
private boolean mShowTitle;
private Callback mPresenterCallback;
private int mInitXOffset;
private int mInitYOffset;
/** Whether the popup has anchor-relative offsets. */
private boolean mHasOffsets;
public MenuPopupHelper(Context context, MenuBuilder menu) {
this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly, int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
mContext = context;
mMenu = menu;
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
mAnchorView = anchorView;
mPopup = createMenuPopup();
}
private MenuPopup createMenuPopup() {
if (mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableCascadingSubmenus)) {
return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
mOverflowOnly);
}
return new StandardMenuPopup(
mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
}
public void setAnchorView(View anchor) {
mAnchorView = anchor;
mPopup.setAnchorView(anchor);
}
public void setForceShowIcon(boolean forceShow) {
mForceShowIcon = forceShow;
mPopup.setForceShowIcon(forceShow);
}
public void setGravity(int gravity) {
mDropDownGravity = gravity;
mPopup.setGravity(gravity);
}
public int getGravity() {
return mDropDownGravity;
}
public void show() {
if (!tryShow()) {
throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
}
}
public void show(int x, int y) {
if (!tryShow(x, y)) {
throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
}
}
public ShowableListMenu getPopup() {
return mPopup;
}
/**
* Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
*
* @return {@code true} if the popup was shown or was already showing prior to calling this
* method, {@code false} otherwise
*/
public boolean tryShow() {
if (isShowing()) {
return true;
}
if (mAnchorView == null) {
return false;
}
mInitXOffset = 0;
mInitYOffset = 0;
mHasOffsets = false;
mShowTitle = false;
showPopup();
return true;
}
/**
* Shows the popup menu and makes a best-effort to anchor it to the
* specified (x,y) coordinate relative to the anchor view.
* <p>
* If the popup's resolved gravity is {@link Gravity#LEFT}, this will
* display the popup with its top-left corner at (x,y) relative to the
* anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
* popup's top-right corner will be at (x,y).
* <p>
* If the popup cannot be displayed fully on-screen, this method will
* attempt to scroll the anchor view's ancestors and/or offset the popup
* such that it may be displayed fully on-screen.
*
* @param x x coordinate relative to the anchor view
* @param y y coordinate relative to the anchor view
* @return {@code true} if the popup was shown or was already showing prior
* to calling this method, {@code false} otherwise
*/
public boolean tryShow(int x, int y) {
if (isShowing()) {
return true;
}
if (mAnchorView == null) {
return false;
}
mInitXOffset = x;
mInitYOffset = y;
mHasOffsets = true;
mShowTitle = true;
showPopup();
return true;
}
private void showPopup() {
mPopup = createMenuPopup();
mPopup.setAnchorView(mAnchorView);
mPopup.setCallback(mPresenterCallback);
mPopup.setForceShowIcon(mForceShowIcon);
mPopup.setGravity(mDropDownGravity);
mPopup.setShowTitle(mShowTitle);
if (mHasOffsets) {
// If the resolved drop-down gravity is RIGHT, the popup's right
// edge will be aligned with the anchor view. Adjust by the anchor
// width such that the top-right corner is at the X offset.
final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
final int resolvedXOffset;
if (hgrav == Gravity.RIGHT) {
resolvedXOffset = mInitXOffset - mAnchorView.getWidth();
} else {
resolvedXOffset = mInitXOffset;
}
mPopup.setHorizontalOffset(resolvedXOffset);
mPopup.setVerticalOffset(mInitYOffset);
}
// In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface,
// we must set the listener to this outer Helper rather than to the inner MenuPopup.
// Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done
// its own handling.
mPopup.setOnDismissListener(this);
mPopup.addMenu(mMenu);
mPopup.show();
}
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
@Override
public void onDismiss() {
mPopup = null;
}
public boolean isShowing() {
return mPopup != null && mPopup.isShowing();
}
public void setCallback(MenuPresenter.Callback cb) {
mPresenterCallback = cb;
mPopup.setCallback(cb);
}
}