/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.common.api;
import com.android.sdklib.annotations.Nullable;
import java.util.Map;
/**
* A menu action represents one item in the context menu displayed by the GLE canvas.
*
* Each action should have a reasonably unique ID. By default actions are stored using
* the lexicographical order of the IDs.
* Duplicated IDs will be ignored -- that is the first one found will be used.
*
* When the canvas has a multiple selection, only actions that are present in all
* the selected nodes are shown. Moreover, for a given ID all actions must be equal, for
* example they must have the same title and choice but not necessarily the same selection.
* This allows the canvas to only display compatible actions that will work on all selected
* elements.
*
* Actions can be grouped in sub-menus if necessary. Whether groups (sub-menus) can contain
* other groups is implementation dependent. Currently the canvas does not support this, but
* we may decide to change this behavior later if deemed useful.
*
* All actions and groups are sorted by their ID, using String's natural sorting order.
* The only way to change this sorting is by choosing the IDs so they the result end up
* sorted as you want it.
*
* The {@link MenuAction} is abstract. Users should instantiate either {@link Toggle},
* {@link Choices} or {@link Group} instead. These classes are immutable.
*
* NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.
*
*/
public abstract class MenuAction {
/**
* The unique id of the action.
* @see #getId()
*/
private final String mId;
/**
* The UI-visible title of the action.
*/
private final String mTitle;
/**
* Creates a new {@link MenuAction} with the given id and the given title.
* Actions which have the same id and the same title are deemed equivalent.
*
* @param id The unique id of the action, which must be similar for all actions that
* perform the same task. Cannot be null.
* @param title The UI-visible title of the action.
*/
private MenuAction(String id, String title) {
mId = id;
mTitle = title;
}
/**
* Returns the unique id of the action. In the context of a multiple selection,
* actions which have the same id are collapsed together and must represent the same
* action. Cannot be null.
*/
public String getId() {
return mId;
}
/**
* Returns the UI-visible title of the action, shown in the context menu.
* Cannot be null.
*/
public String getTitle() {
return mTitle;
}
/**
* Actions which have the same id and the same title are deemed equivalent.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof MenuAction) {
MenuAction rhs = (MenuAction) obj;
if (mId != rhs.mId && !(mId != null && mId.equals(rhs.mId))) return false;
if (mTitle != rhs.mTitle &&
!(mTitle != null && mTitle.equals(rhs.mTitle))) return false;
return true;
}
return false;
}
/**
* Actions which have the same id and the same title have the same hash code.
*/
@Override
public int hashCode() {
int h = mId == null ? 0 : mId.hashCode();
h = h ^ (mTitle == null ? 0 : mTitle.hashCode());
return h;
}
/**
* A group of actions, displayed in a sub-menu.
*
* Note that group can be seen as a "group declaration": the group does not hold a list
* actions that it will contain. This merely let the canvas create a sub-menu with the
* given title and actions that define this group-id will be placed in the sub-menu.
*
* The current canvas has the following implementation details:
* - There's only one level of sub-menu.
* That is you can't have a sub-menu inside another sub-menu.
* This is expressed by the fact that groups do not have a parent group-id.
* - It is not currently necessary to define a group before defining actions that refer
* to that group. Moreover, in the context of a multiple selection, one view could
* contribute actions to any group even if created by another view. Both practices
* are discouraged.
* - Actions which group-id do not match any known group will simply be placed in the
* root context menu.
* - Empty groups do not create visible sub-menus.
* These implementations details may change in the future and should not be relied upon.
*/
public static class Group extends MenuAction {
/**
* Constructs a new group of actions.
*
* @param id The id of the group. Must be unique. Cannot be null.
* @param title The UI-visible title of the group, shown in the sub-menu.
*/
public Group(String id, String title) {
super(id, title);
}
}
/**
* The base class for {@link Toggle} and {@link Choices}.
*/
public static abstract class Action extends MenuAction {
/**
* A callback executed when the action is selected in the context menu.
*/
private final IMenuCallback mCallback;
/**
* An optional group id, to place the action in a given sub-menu.
* @null This value can be null.
*/
@Nullable
private final String mGroupId;
/**
* Constructs a new base {@link MenuAction} with its ID, title and action callback.
*
* @param id The unique ID of the action. Must not be null.
* @param title The title of the action. Must not be null.
* @param groupId The optional group id, to place the action in a given sub-menu.
* Can be null.
* @param callback The callback executed when the action is selected.
* Must not be null.
*/
private Action(String id, String title, String groupId, IMenuCallback callback) {
super(id, title);
mGroupId = groupId;
mCallback = callback;
}
/**
* Returns the callback executed when the action is selected in the
* context menu. Cannot be null.
*/
public IMenuCallback getCallback() {
return mCallback;
}
/**
* Returns the optional id of an existing group or null
* @null This value can be null.
*/
@Nullable
public String getGroupId() {
return mGroupId;
}
/**
* Two actions are equal if the have the same id, title and group-id.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Action && super.equals(obj)) {
Action rhs = (Action) obj;
return mGroupId == rhs.mGroupId ||
(mGroupId != null && mGroupId.equals(rhs.mGroupId));
}
return false;
}
/**
* Two actions have the same hash code if the have the same id, title and group-id.
*/
@Override
public int hashCode() {
int h = super.hashCode();
h = h ^ (mGroupId == null ? 0 : mGroupId.hashCode());
return h;
}
}
/**
* A toggle is a simple on/off action, displayed as an item in a context menu
* with a check mark if the item is checked.
*
* Two toggles are equal if they have the same id, title and group-id.
* It is expected for the checked state and action callback to be different.
*/
public static class Toggle extends Action {
/**
* True if the item is displayed with a check mark.
*/
final private boolean mIsChecked;
/**
* Creates a new immutable toggle action.
* This action has no group-id and will show up in the root of the context menu.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param isChecked Whether the context menu item has a check mark.
* @param callback A callback to execute when the context menu item is
* selected.
*/
public Toggle(String id, String title, boolean isChecked, IMenuCallback callback) {
this(id, title, isChecked, null /* group-id */, callback);
}
/**
* Creates a new immutable toggle action.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param isChecked Whether the context menu item has a check mark.
* @param groupId The optional group id, to place the action in a given sub-menu.
* Can be null.
* @param callback A callback to execute when the context menu item is
* selected.
*/
public Toggle(String id, String title, boolean isChecked, String groupId,
IMenuCallback callback) {
super(id, title, groupId, callback);
mIsChecked = isChecked;
}
/**
* Returns true if the item is displayed with a check mark.
*/
public boolean isChecked() {
return mIsChecked;
}
/**
* Two toggles are equal if they have the same id, title and group-id.
* It is acceptable for the checked state and action callback to be different.
*/
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* Two toggles have the same hash code if they have the same id, title and group-id.
*/
@Override
public int hashCode() {
return super.hashCode();
}
}
/**
* A "choices" is a one-out-of-many-choices action, displayed as a sub-menu with one or more
* items, with either zero or more of them being checked.
*
* Implementation detail: empty choices will not be displayed in the context menu.
*
* Choice items are sorted by their id, using String's natural sorting order.
*
* Two multiple choices are equal if they have the same id, title, group-id and choices.
* It is expected for the current state and action callback to be different.
*/
public static class Choices extends Action {
/**
* Special value which will insert a separator in the choices' submenu.
*/
public final static String SEPARATOR = "----";
/**
* Character used to split multiple checked choices, see {@link #getCurrent()}.
* The pipe character "|" is used, to natively match Android resource flag separators.
*/
public final static String CHOICE_SEP = "|";
/**
* A non-null map of id=>choice-title. The map could be empty but not null.
*/
private final Map mChoices;
/**
* One or more id for the checked choice(s) that will be check marked.
* Can be null. Can be an id not present in the choices map.
* If more than one choice, they must be separated by {@link #CHOICE_SEP}.
*/
private final String mCurrent;
/**
* Creates a new immutable multiple-choice action.
* This action has no group-id and will show up in the root of the context menu.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param choices A map id=>title for all the multiple-choice items. Cannot be null.
* @param current The id(s) of the current choice(s) that will be check marked.
* Can be null. Can be an id not present in the choices map.
* There can be more than one id separated by {@link #CHOICE_SEP}.
* @param callback A callback to execute when the context menu item is
* selected.
*/
public Choices(String id, String title,
Map choices,
String current,
IMenuCallback callback) {
this(id, title, choices, current, null /* group-id */, callback);
}
/**
* Creates a new immutable multiple-choice action.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param choices A map id=>title for all the multiple-choice items. Cannot be null.
* @param current The id(s) of the current choice(s) that will be check marked.
* Can be null. Can be an id not present in the choices map.
* There can be more than one id separated by {@link #CHOICE_SEP}.
* @param groupId The optional group id, to place the action in a given sub-menu.
* Can be null.
* @param callback A callback to execute when the context menu item is
* selected.
*/
public Choices(String id, String title,
Map choices,
String current,
String groupId,
IMenuCallback callback) {
super(id, title, groupId, callback);
mChoices = choices;
mCurrent = current;
}
/**
* Return the map of id=>choice-title. The map could be empty but not null.
*/
public Map getChoices() {
return mChoices;
}
/**
* Returns the id(s) of the current choice(s) that are check marked.
* Can be null. Can be an id not present in the choices map.
* There can be more than one id separated by {@link #CHOICE_SEP}.
*/
public String getCurrent() {
return mCurrent;
}
/**
* Two multiple choices are equal if they have the same id, title, group-id and choices.
* It is acceptable for the current state and action callback to be different.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Choices && super.equals(obj)) {
Choices rhs = (Choices) obj;
return mChoices.equals(rhs.mChoices);
}
return false;
}
/**
* Two multiple choices have the same hash code if they have the same id, title,
* group-id and choices.
*/
@Override
public int hashCode() {
int h = super.hashCode();
if (mChoices != null) {
h = h ^ mChoices.hashCode();
}
return h;
}
}
}