/*
* Copyright (C) 2011 Google Inc.
*
* 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.talkback.contextmenu;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.PointF;
import android.view.Menu;
import com.android.talkback.R;
import com.android.utils.SparseIterableArray;
/**
* Implements a radial menu with up to four corners.
*
* @see Menu
*/
public class RadialMenu extends ContextMenu {
private static final int ORDER_NW = 0;
private static final int ORDER_NE = 1;
private static final int ORDER_SW = 2;
private static final int ORDER_SE = 3;
private final DialogInterface mParent;
private final SparseIterableArray<RadialMenuItem> mCorners;
private RadialMenuItem.OnMenuItemSelectionListener mSelectionListener;
private OnMenuVisibilityChangedListener mVisibilityListener;
private MenuLayoutListener mLayoutListener;
/**
* Creates a new radial menu with the specified parent dialog interface.
*
* @param context Current context
* @param parent to the menu
*/
public RadialMenu(Context context, DialogInterface parent) {
super(context);
mParent = parent;
mCorners = new SparseIterableArray<>();
}
public void setLayoutListener(MenuLayoutListener layoutListener) {
mLayoutListener = layoutListener;
}
/**
* Sets the default selection listener for menu items. If the default
* selection listener returns false for an event, it will be passed to the
* menu's parent menu (if any).
*
* @param selectionListener The default selection listener for menu items.
*/
public void setDefaultSelectionListener(
RadialMenuItem.OnMenuItemSelectionListener selectionListener) {
mSelectionListener = selectionListener;
}
public void setOnMenuVisibilityChangedListener(OnMenuVisibilityChangedListener listener) {
mVisibilityListener = listener;
}
@Override
public RadialMenuItem add(int groupId, int itemId, int order, CharSequence title) {
final RadialMenuItem item = new RadialMenuItem(getContext(), groupId, itemId, order, title);
return addItem(item);
}
/**
* Add a pre-constructed {@link RadialMenuItem} to the menu.
*
* @param item The item to add.
*/
private RadialMenuItem addItem(RadialMenuItem item) {
// If this is a sub-menu, add the default selection and click listeners.
if (item.hasSubMenu()) {
item.setOnMenuItemSelectionListener(mSelectionListener);
item.setOnMenuItemClickListener(getDefaultListener());
}
if (item.getGroupId() == R.id.group_corners) {
item.setCorner();
mCorners.put(item.getOrder(), item);
} else {
add(item);
}
onLayoutChanged();
return item;
}
/**
* Adds all items from a collection of {@link RadialMenuItem}s to the menu.
*
* @param items The items to add.
*/
public void addAll(Iterable<? extends RadialMenuItem> items) {
for (RadialMenuItem item : items) {
addItem(item);
}
}
@Override
public RadialSubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
final RadialSubMenu submenu = new RadialSubMenu(
getContext(), mParent, this, groupId, itemId, order, title);
addItem(submenu.getItem());
return submenu;
}
/*package*/ void onShow() {
if (mVisibilityListener != null) {
mVisibilityListener.onMenuShown();
}
}
/*package*/ void onDismiss() {
if (mVisibilityListener != null) {
mVisibilityListener.onMenuDismissed();
}
}
@Override
public void close() {
onDismiss();
mParent.dismiss();
}
@Override
public ContextMenuItemBuilder getMenuItemBuilder() {
return new ContextMenuItemBuilder() {
@Override
public ContextMenuItem createMenuItem(Context context, int groupId, int itemId,
int order, CharSequence title) {
return new RadialMenuItem(context, groupId, itemId, order, title);
}
};
}
@Override
public RadialMenuItem getItem(int index) {
return (RadialMenuItem) super.getItem(index);
}
/**
* Gets the corner menu item with the given group ID.
*
* @param groupId The corner group ID of the item to be returned. One of:
* <ul>
* <li>{@link RadialMenu#ORDER_NW}
* <li>{@link RadialMenu#ORDER_NE}
* <li>{@link RadialMenu#ORDER_SE}
* <li>{@link RadialMenu#ORDER_SW}
* </ul>
* @return The corner menu item.
*/
public RadialMenuItem getCorner(int groupId) {
return mCorners.get(groupId);
}
/**
* Returns the rotation of a corner in degrees.
*
* @param groupId The corner group ID of the item to be returned. One of:
* <ul>
* <li>{@link RadialMenu#ORDER_NW}
* <li>{@link RadialMenu#ORDER_NE}
* <li>{@link RadialMenu#ORDER_SE}
* <li>{@link RadialMenu#ORDER_SW}
* </ul>
* @return The rotation of a corner in degrees.
*/
/* package */ static float getCornerRotation(int groupId) {
final float rotation;
switch (groupId) {
case RadialMenu.ORDER_NW:
rotation = 135;
break;
case RadialMenu.ORDER_NE:
rotation = -135;
break;
case RadialMenu.ORDER_SE:
rotation = -45;
break;
case RadialMenu.ORDER_SW:
rotation = 45;
break;
default:
rotation = 0;
}
return rotation;
}
/**
* Returns the on-screen location of a corner as percentages of the
* screen size. The resulting point coordinates should be multiplied by
* screen width and height.
*
* @param groupId The corner group ID of the item to be returned. One of:
* <ul>
* <li>{@link RadialMenu#ORDER_NW}
* <li>{@link RadialMenu#ORDER_NE}
* <li>{@link RadialMenu#ORDER_SE}
* <li>{@link RadialMenu#ORDER_SW}
* </ul>
* @return The on-screen location of a corner as percentages of the
* screen size.
*/
static PointF getCornerLocation(int groupId) {
final float x;
final float y;
switch (groupId) {
case RadialMenu.ORDER_NW:
x = 0;
y = 0;
break;
case RadialMenu.ORDER_NE:
x = 1;
y = 0;
break;
case RadialMenu.ORDER_SE:
x = 1;
y = 1;
break;
case RadialMenu.ORDER_SW:
x = 0;
y = 1;
break;
default:
return null;
}
return new PointF(x, y);
}
@Override
public void removeItem(int id) {
super.removeItem(id);
onLayoutChanged();
}
/**
* Performs the selection action associated with a particular
* {@link RadialMenuItem}.
*
* @param item to select
* @param flags unused
* @return {@code true} if the menu item performs an action
*/
public boolean selectMenuItem(RadialMenuItem item, int flags) {
return ((item != null) && item.onSelectionPerformed())
|| ((mSelectionListener != null) && mSelectionListener.onMenuItemSelection(item));
}
public boolean clearSelection(int flags) {
return selectMenuItem(null, flags);
}
@Override
public void removeGroup(int group) {
super.removeGroup(group);
onLayoutChanged();
}
@Override
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
super.setGroupCheckable(group, checkable, exclusive);
onLayoutChanged();
}
@Override
public void setGroupEnabled(int group, boolean enabled) {
super.setGroupEnabled(group, enabled);
onLayoutChanged();
}
@Override
public void setGroupVisible(int group, boolean visible) {
super.setGroupVisible(group, visible);
onLayoutChanged();
}
@Override
public RadialMenuItem findItem(int id) {
RadialMenuItem menuItem = (RadialMenuItem) super.findItem(id);
if (menuItem == null) {
for (RadialMenuItem item : mCorners) {
if (item.getItemId() == id) {
return item;
}
}
}
return menuItem;
}
void onLayoutChanged() {
if (mLayoutListener != null) {
mLayoutListener.onLayoutChanged();
}
}
public interface MenuLayoutListener {
void onLayoutChanged();
}
/**
* Interface definition for a callback to be invoked when a menu is shown or
* dismissed.
*/
public interface OnMenuVisibilityChangedListener {
/**
* Called when a menu has been displayed.
*/
void onMenuShown();
/**
* Called when a menu has been dismissed.
*/
void onMenuDismissed();
}
}