You've already forked android_translation_layer
mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-10-27 11:48:10 -07:00
PopupMenu: use AOSP MenuInflater and support item visibility
This commit is contained in:
@@ -17,11 +17,27 @@ JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1init
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: android_widget_PopupMenu
|
* Class: android_widget_PopupMenu
|
||||||
* Method: native_appendItem
|
* Method: native_insertItem
|
||||||
* Signature: (JLjava/lang/String;I)V
|
* Signature: (JILjava/lang/String;I)V
|
||||||
*/
|
*/
|
||||||
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1appendItem
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1insertItem
|
||||||
(JNIEnv *, jobject, jlong, jstring, jint);
|
(JNIEnv *, jobject, jlong, jint, jstring, jint);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: android_widget_PopupMenu
|
||||||
|
* Method: native_insertSubmenu
|
||||||
|
* Signature: (JILjava/lang/String;J)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1insertSubmenu
|
||||||
|
(JNIEnv *, jobject, jlong, jint, jstring, jlong);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: android_widget_PopupMenu
|
||||||
|
* Method: native_removeItem
|
||||||
|
* Signature: (JI)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1removeItem
|
||||||
|
(JNIEnv *, jobject, jlong, jint);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: android_widget_PopupMenu
|
* Class: android_widget_PopupMenu
|
||||||
|
|||||||
@@ -10,13 +10,30 @@ JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1init(JNIEnv *env,
|
|||||||
return _INTPTR(g_menu_new());
|
return _INTPTR(g_menu_new());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1appendItem(JNIEnv *env, jobject this, jlong menu_ptr, jstring title_jstr, jint id)
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1insertItem(JNIEnv *env, jobject this, jlong menu_ptr, jint position, jstring title_jstr, jint id)
|
||||||
{
|
{
|
||||||
const gchar *title = (*env)->GetStringUTFChars(env, title_jstr, NULL);
|
const gchar *title = (*env)->GetStringUTFChars(env, title_jstr, NULL);
|
||||||
|
printf("insertItem position: %d title: %s\n", position, title);
|
||||||
GMenuItem *item = g_menu_item_new(title, NULL);
|
GMenuItem *item = g_menu_item_new(title, NULL);
|
||||||
(*env)->ReleaseStringUTFChars(env, title_jstr, title);
|
(*env)->ReleaseStringUTFChars(env, title_jstr, title);
|
||||||
g_menu_item_set_action_and_target(item, "popupmenu.clicked", "i", id);
|
g_menu_item_set_action_and_target(item, "popupmenu.clicked", "i", id);
|
||||||
g_menu_append_item(G_MENU(_PTR(menu_ptr)), item);
|
g_menu_insert_item(G_MENU(_PTR(menu_ptr)), position, item);
|
||||||
|
g_object_unref(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1insertSubmenu(JNIEnv *env, jobject this, jlong menu_ptr, jint position, jstring title_jstr, jlong submenu_ptr)
|
||||||
|
{
|
||||||
|
const gchar *title = (*env)->GetStringUTFChars(env, title_jstr, NULL);
|
||||||
|
printf("insertSubmenu position: %d title: %s\n", position, title);
|
||||||
|
GMenuItem *item = g_menu_item_new_submenu(title, G_MENU_MODEL(_PTR(submenu_ptr)));
|
||||||
|
(*env)->ReleaseStringUTFChars(env, title_jstr, title);
|
||||||
|
g_menu_insert_item(G_MENU(_PTR(menu_ptr)), position, item);
|
||||||
|
g_object_unref(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1removeItem(JNIEnv *env, jobject this, jlong menu_ptr, jint position)
|
||||||
|
{
|
||||||
|
g_menu_remove(G_MENU(_PTR(menu_ptr)), position);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void popupmenu_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
static void popupmenu_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
package android.view;
|
package android.view;
|
||||||
|
|
||||||
public class InflateException extends Exception {
|
public class InflateException extends RuntimeException {
|
||||||
|
|
||||||
|
public InflateException(String string, Exception e) {
|
||||||
|
super(string, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InflateException(String string) {
|
||||||
|
super(string);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,455 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 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 android.view;
|
package android.view;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.ContextWrapper;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.content.res.XmlResourceParser;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Xml;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to instantiate menu XML files into Menu objects.
|
||||||
|
* <p>
|
||||||
|
* For performance reasons, menu inflation relies heavily on pre-processing of
|
||||||
|
* XML files that is done at build time. Therefore, it is not currently possible
|
||||||
|
* to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
|
||||||
|
* it only works with an XmlPullParser returned from a compiled resource (R.
|
||||||
|
* <em>something</em> file.)
|
||||||
|
*/
|
||||||
public class MenuInflater {
|
public class MenuInflater {
|
||||||
|
private static final String LOG_TAG = "MenuInflater";
|
||||||
|
|
||||||
public MenuInflater(Context context) {}
|
/** Menu tag name in XML. */
|
||||||
|
private static final String XML_MENU = "menu";
|
||||||
|
|
||||||
public void inflate(int menuRes, Menu menu) {}
|
/** Group tag name in XML. */
|
||||||
|
private static final String XML_GROUP = "group";
|
||||||
|
|
||||||
|
/** Item tag name in XML. */
|
||||||
|
private static final String XML_ITEM = "item";
|
||||||
|
|
||||||
|
private static final int NO_ID = 0;
|
||||||
|
|
||||||
|
private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class};
|
||||||
|
|
||||||
|
private final Object[] mActionViewConstructorArguments;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private Object mRealOwner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a menu inflater.
|
||||||
|
*
|
||||||
|
* @see Activity#getMenuInflater()
|
||||||
|
*/
|
||||||
|
public MenuInflater(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
mActionViewConstructorArguments = new Object[] {context};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a menu inflater.
|
||||||
|
*
|
||||||
|
* @see Activity#getMenuInflater()
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public MenuInflater(Context context, Object realOwner) {
|
||||||
|
mContext = context;
|
||||||
|
mRealOwner = realOwner;
|
||||||
|
mActionViewConstructorArguments = new Object[] {context};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflate a menu hierarchy from the specified XML resource. Throws
|
||||||
|
* {@link InflateException} if there is an error.
|
||||||
|
*
|
||||||
|
* @param menuRes Resource ID for an XML layout resource to load (e.g.,
|
||||||
|
* <code>R.menu.main_activity</code>)
|
||||||
|
* @param menu The Menu to inflate into. The items and submenus will be
|
||||||
|
* added to this Menu.
|
||||||
|
*/
|
||||||
|
public void inflate(int menuRes, Menu menu) {
|
||||||
|
XmlResourceParser parser = null;
|
||||||
|
try {
|
||||||
|
parser = mContext.getResources().getLayout(menuRes);
|
||||||
|
AttributeSet attrs = Xml.asAttributeSet(parser);
|
||||||
|
|
||||||
|
parseMenu(parser, attrs, menu);
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
throw new InflateException("Error inflating menu XML", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InflateException("Error inflating menu XML", e);
|
||||||
|
} finally {
|
||||||
|
if (parser != null) parser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called internally to fill the given menu. If a sub menu is seen, it will
|
||||||
|
* call this recursively.
|
||||||
|
*/
|
||||||
|
private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
MenuState menuState = new MenuState(menu);
|
||||||
|
|
||||||
|
int eventType = parser.getEventType();
|
||||||
|
String tagName;
|
||||||
|
boolean lookingForEndOfUnknownTag = false;
|
||||||
|
String unknownTagName = null;
|
||||||
|
|
||||||
|
// This loop will skip to the menu start tag
|
||||||
|
do {
|
||||||
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
|
tagName = parser.getName();
|
||||||
|
if (tagName.equals(XML_MENU)) {
|
||||||
|
// Go to next tag
|
||||||
|
eventType = parser.next();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Expecting menu, got " + tagName);
|
||||||
|
}
|
||||||
|
eventType = parser.next();
|
||||||
|
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||||
|
|
||||||
|
boolean reachedEndOfMenu = false;
|
||||||
|
while (!reachedEndOfMenu) {
|
||||||
|
switch (eventType) {
|
||||||
|
case XmlPullParser.START_TAG:
|
||||||
|
if (lookingForEndOfUnknownTag) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tagName = parser.getName();
|
||||||
|
if (tagName.equals(XML_GROUP)) {
|
||||||
|
menuState.readGroup(attrs);
|
||||||
|
} else if (tagName.equals(XML_ITEM)) {
|
||||||
|
menuState.readItem(attrs);
|
||||||
|
} else if (tagName.equals(XML_MENU)) {
|
||||||
|
// A menu start tag denotes a submenu for an item
|
||||||
|
SubMenu subMenu = menuState.addSubMenuItem();
|
||||||
|
registerMenu(subMenu, attrs);
|
||||||
|
|
||||||
|
// Parse the submenu into returned SubMenu
|
||||||
|
parseMenu(parser, attrs, subMenu);
|
||||||
|
} else {
|
||||||
|
lookingForEndOfUnknownTag = true;
|
||||||
|
unknownTagName = tagName;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XmlPullParser.END_TAG:
|
||||||
|
tagName = parser.getName();
|
||||||
|
if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
|
||||||
|
lookingForEndOfUnknownTag = false;
|
||||||
|
unknownTagName = null;
|
||||||
|
} else if (tagName.equals(XML_GROUP)) {
|
||||||
|
menuState.resetGroup();
|
||||||
|
} else if (tagName.equals(XML_ITEM)) {
|
||||||
|
// Add the item if it hasn't been added (if the item was
|
||||||
|
// a submenu, it would have been added already)
|
||||||
|
if (!menuState.hasAddedItem()) {
|
||||||
|
registerMenu(menuState.addItem(), attrs);
|
||||||
|
}
|
||||||
|
} else if (tagName.equals(XML_MENU)) {
|
||||||
|
reachedEndOfMenu = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XmlPullParser.END_DOCUMENT:
|
||||||
|
throw new RuntimeException("Unexpected end of document");
|
||||||
|
}
|
||||||
|
|
||||||
|
eventType = parser.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method is a hook for layoutlib to do its magic.
|
||||||
|
* Nothing is needed outside of LayoutLib. However, it should not be deleted because it
|
||||||
|
* appears to do nothing.
|
||||||
|
*/
|
||||||
|
private void registerMenu(@SuppressWarnings("unused") MenuItem item,
|
||||||
|
@SuppressWarnings("unused") AttributeSet set) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method is a hook for layoutlib to do its magic.
|
||||||
|
* Nothing is needed outside of LayoutLib. However, it should not be deleted because it
|
||||||
|
* appears to do nothing.
|
||||||
|
*/
|
||||||
|
private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
|
||||||
|
@SuppressWarnings("unused") AttributeSet set) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed by layoutlib.
|
||||||
|
/*package*/ Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InflatedOnMenuItemClickListener
|
||||||
|
implements MenuItem.OnMenuItemClickListener {
|
||||||
|
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
|
||||||
|
|
||||||
|
private Object mRealOwner;
|
||||||
|
private Method mMethod;
|
||||||
|
|
||||||
|
public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
|
||||||
|
mRealOwner = realOwner;
|
||||||
|
Class<?> c = realOwner.getClass();
|
||||||
|
try {
|
||||||
|
mMethod = c.getMethod(methodName, PARAM_TYPES);
|
||||||
|
} catch (Exception e) {
|
||||||
|
InflateException ex = new InflateException(
|
||||||
|
"Couldn't resolve menu item onClick handler " + methodName +
|
||||||
|
" in class " + c.getName());
|
||||||
|
ex.initCause(e);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
try {
|
||||||
|
if (mMethod.getReturnType() == Boolean.TYPE) {
|
||||||
|
return (Boolean) mMethod.invoke(mRealOwner, item);
|
||||||
|
} else {
|
||||||
|
mMethod.invoke(mRealOwner, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getRealOwner() {
|
||||||
|
if (mRealOwner == null) {
|
||||||
|
mRealOwner = findRealOwner(mContext);
|
||||||
|
}
|
||||||
|
return mRealOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object findRealOwner(Object owner) {
|
||||||
|
if (owner instanceof Activity) {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
if (owner instanceof ContextWrapper) {
|
||||||
|
return findRealOwner(((ContextWrapper) owner).getBaseContext());
|
||||||
|
}
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the current menu.
|
||||||
|
* <p>
|
||||||
|
* Groups can not be nested unless there is another menu (which will have
|
||||||
|
* its state class).
|
||||||
|
*/
|
||||||
|
private class MenuState {
|
||||||
|
private Menu menu;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Group state is set on items as they are added, allowing an item to
|
||||||
|
* override its group state. (As opposed to set on items at the group end tag.)
|
||||||
|
*/
|
||||||
|
private int groupId;
|
||||||
|
private int groupCheckable;
|
||||||
|
private boolean groupVisible;
|
||||||
|
private boolean groupEnabled;
|
||||||
|
|
||||||
|
private boolean itemAdded;
|
||||||
|
private int itemId;
|
||||||
|
private int itemCategoryOrder;
|
||||||
|
private CharSequence itemTitle;
|
||||||
|
private CharSequence itemTitleCondensed;
|
||||||
|
private int itemIconResId;
|
||||||
|
/**
|
||||||
|
* Sync to attrs.xml enum:
|
||||||
|
* - 0: none
|
||||||
|
* - 1: all
|
||||||
|
* - 2: exclusive
|
||||||
|
*/
|
||||||
|
private int itemCheckable;
|
||||||
|
private boolean itemChecked;
|
||||||
|
private boolean itemVisible;
|
||||||
|
private boolean itemEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync to attrs.xml enum, values in MenuItem:
|
||||||
|
* - 0: never
|
||||||
|
* - 1: ifRoom
|
||||||
|
* - 2: always
|
||||||
|
* - -1: Safe sentinel for "no value".
|
||||||
|
*/
|
||||||
|
private int itemShowAsAction;
|
||||||
|
|
||||||
|
private int itemActionViewLayout;
|
||||||
|
private String itemActionViewClassName;
|
||||||
|
|
||||||
|
private String itemListenerMethodName;
|
||||||
|
|
||||||
|
private static final int defaultGroupId = NO_ID;
|
||||||
|
private static final int defaultItemId = NO_ID;
|
||||||
|
private static final int defaultItemCheckable = 0;
|
||||||
|
private static final boolean defaultItemChecked = false;
|
||||||
|
private static final boolean defaultItemVisible = true;
|
||||||
|
private static final boolean defaultItemEnabled = true;
|
||||||
|
|
||||||
|
public MenuState(final Menu menu) {
|
||||||
|
this.menu = menu;
|
||||||
|
|
||||||
|
resetGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetGroup() {
|
||||||
|
groupId = defaultGroupId;
|
||||||
|
groupCheckable = defaultItemCheckable;
|
||||||
|
groupVisible = defaultItemVisible;
|
||||||
|
groupEnabled = defaultItemEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the parser is pointing to a group tag.
|
||||||
|
*/
|
||||||
|
public void readGroup(AttributeSet attrs) {
|
||||||
|
TypedArray a = mContext.obtainStyledAttributes(attrs,
|
||||||
|
com.android.internal.R.styleable.MenuGroup);
|
||||||
|
|
||||||
|
groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
|
||||||
|
groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
|
||||||
|
groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
|
||||||
|
groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the parser is pointing to an item tag.
|
||||||
|
*/
|
||||||
|
public void readItem(AttributeSet attrs) {
|
||||||
|
TypedArray a = mContext.obtainStyledAttributes(attrs,
|
||||||
|
com.android.internal.R.styleable.MenuItem);
|
||||||
|
|
||||||
|
// Inherit attributes from the group as default value
|
||||||
|
itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
|
||||||
|
itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);
|
||||||
|
itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);
|
||||||
|
itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
|
||||||
|
|
||||||
|
if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
|
||||||
|
// Item has attribute checkable, use it
|
||||||
|
itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
// Item does not have attribute, use the group's (group can have one more state
|
||||||
|
// for checkable that represents the exclusive checkable)
|
||||||
|
itemCheckable = groupCheckable;
|
||||||
|
}
|
||||||
|
itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
|
||||||
|
itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
|
||||||
|
itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
|
||||||
|
itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1);
|
||||||
|
itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
|
||||||
|
itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);
|
||||||
|
itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
|
||||||
|
itemAdded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setItem(MenuItem item) {
|
||||||
|
item.setChecked(itemChecked)
|
||||||
|
.setVisible(itemVisible)
|
||||||
|
.setEnabled(itemEnabled)
|
||||||
|
.setCheckable(itemCheckable >= 1)
|
||||||
|
.setTitleCondensed(itemTitleCondensed)
|
||||||
|
.setIcon(itemIconResId);
|
||||||
|
|
||||||
|
if (itemShowAsAction >= 0) {
|
||||||
|
item.setShowAsAction(itemShowAsAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemListenerMethodName != null) {
|
||||||
|
if (mContext.isRestricted()) {
|
||||||
|
throw new IllegalStateException("The android:onClick attribute cannot "
|
||||||
|
+ "be used within a restricted context");
|
||||||
|
}
|
||||||
|
item.setOnMenuItemClickListener(
|
||||||
|
new InflatedOnMenuItemClickListener(getRealOwner(), itemListenerMethodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean actionViewSpecified = false;
|
||||||
|
if (itemActionViewClassName != null) {
|
||||||
|
View actionView = (View) newInstance(itemActionViewClassName,
|
||||||
|
ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments);
|
||||||
|
item.setActionView(actionView);
|
||||||
|
actionViewSpecified = true;
|
||||||
|
}
|
||||||
|
if (itemActionViewLayout > 0) {
|
||||||
|
if (!actionViewSpecified) {
|
||||||
|
actionViewSpecified = true;
|
||||||
|
} else {
|
||||||
|
Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'."
|
||||||
|
+ " Action view already specified.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuItem addItem() {
|
||||||
|
itemAdded = true;
|
||||||
|
MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
|
||||||
|
setItem(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubMenu addSubMenuItem() {
|
||||||
|
itemAdded = true;
|
||||||
|
SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
|
||||||
|
setItem(subMenu.getItem());
|
||||||
|
return subMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAddedItem() {
|
||||||
|
return itemAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T newInstance(String className, Class<?>[] constructorSignature,
|
||||||
|
Object[] arguments) {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = mContext.getClassLoader().loadClass(className);
|
||||||
|
Constructor<?> constructor = clazz.getConstructor(constructorSignature);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return (T) constructor.newInstance(arguments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
package android.widget;
|
package android.widget;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.content.res.XmlResourceParser;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -28,7 +26,8 @@ import android.view.SubMenu;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnTouchListener;
|
import android.view.View.OnTouchListener;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
|
||||||
@@ -47,6 +46,7 @@ public class PopupMenu {
|
|||||||
private OnDismissListener mOnDismissListener;
|
private OnDismissListener mOnDismissListener;
|
||||||
private OnTouchListener mDragListener;
|
private OnTouchListener mDragListener;
|
||||||
private long popover;
|
private long popover;
|
||||||
|
private MenuImpl menu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to create a new popup menu with an anchor view.
|
* Constructor to create a new popup menu with an anchor view.
|
||||||
@@ -94,6 +94,8 @@ public class PopupMenu {
|
|||||||
int popupStyleRes) {
|
int popupStyleRes) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mAnchor = anchor;
|
mAnchor = anchor;
|
||||||
|
menu = new MenuImpl();
|
||||||
|
popover = native_buildPopover(menu.menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,55 +127,250 @@ public class PopupMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected native long native_init();
|
protected native long native_init();
|
||||||
protected native void native_appendItem(long menu, String item, int id);
|
protected native void native_insertItem(long menu, int position, String item, int id);
|
||||||
|
protected native void native_insertSubmenu(long menu, int position, String item, long submenu);
|
||||||
|
protected native void native_removeItem(long menu, int position);
|
||||||
protected native long native_buildPopover(long menu);
|
protected native long native_buildPopover(long menu);
|
||||||
protected native void native_show(long popover, long anchor);
|
protected native void native_show(long popover, long anchor);
|
||||||
|
|
||||||
// callback from native code
|
// callback from native code
|
||||||
protected void menuItemClickCallback(final int id) {
|
protected void menuItemClickCallback(final int id) {
|
||||||
if (mMenuItemClickListener != null) {
|
if (mMenuItemClickListener != null) {
|
||||||
mMenuItemClickListener.onMenuItemClick(new MenuItem() {
|
mMenuItemClickListener.onMenuItemClick(getMenu().findItem(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflate a menu resource into this PopupMenu. This is equivalent to
|
||||||
|
* calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
|
||||||
|
*
|
||||||
|
* @param menuRes Menu resource to inflate
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void inflate(int menuRes) throws Exception {
|
||||||
|
getMenuInflater().inflate(menuRes, getMenu());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the menu popup anchored to the view specified during construction.
|
||||||
|
*
|
||||||
|
* @see #dismiss()
|
||||||
|
*/
|
||||||
|
public void show() {
|
||||||
|
System.out.println("PopupMenu.show() called");
|
||||||
|
native_show(popover, mAnchor.widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Menu getMenu() {
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss() {
|
||||||
|
System.out.println("PopupMenu.dismiss() called");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface responsible for receiving menu item click events if the items
|
||||||
|
* themselves do not have individual item click listeners.
|
||||||
|
*/
|
||||||
|
public interface OnMenuItemClickListener {
|
||||||
|
/**
|
||||||
|
* This method will be invoked when a menu item is clicked if the item
|
||||||
|
* itself did not already handle the event.
|
||||||
|
*
|
||||||
|
* @param item the menu item that was clicked
|
||||||
|
* @return {@code true} if the event was handled, {@code false}
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
boolean onMenuItemClick(MenuItem item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface used to notify the application that the menu has closed.
|
||||||
|
*/
|
||||||
|
public interface OnDismissListener {
|
||||||
|
/**
|
||||||
|
* Called when the associated menu has been dismissed.
|
||||||
|
*
|
||||||
|
* @param menu the popup menu that was dismissed
|
||||||
|
*/
|
||||||
|
void onDismiss(PopupMenu menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MenuImpl implements Menu {
|
||||||
|
long menu = native_init();
|
||||||
|
|
||||||
|
List<MenuItemImpl> items = new ArrayList<>();
|
||||||
|
int numVisibleItems = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
|
||||||
|
MenuItemImpl item = new MenuItemImpl(itemId, this, String.valueOf(title), null);
|
||||||
|
items.add(item);
|
||||||
|
item.setVisible(true);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'add'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem findItem(int id) {
|
||||||
|
for (MenuItemImpl item : items) {
|
||||||
|
if (item.id == id)
|
||||||
|
return item;
|
||||||
|
if (item.subMenu != null) {
|
||||||
|
MenuItem found = item.subMenu.findItem(id);
|
||||||
|
if (found != null)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem getItem(int id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getItem'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'clear'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeGroup(int groupId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'removeGroup'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubMenu addSubMenu(int id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'addSubMenu'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem add(int id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'add'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem add(CharSequence text) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'add'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setGroupCheckable'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
|
||||||
|
SubMenuImpl submenu = new SubMenuImpl(itemId, this, String.valueOf(title));
|
||||||
|
items.add(submenu.item);
|
||||||
|
submenu.item.setVisible(true);
|
||||||
|
return submenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SubMenuImpl extends MenuImpl implements SubMenu {
|
||||||
|
private MenuItemImpl item;
|
||||||
|
|
||||||
|
public SubMenuImpl(int id, MenuImpl parent, String title) {
|
||||||
|
item = new MenuItemImpl(id, parent, title, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MenuItemImpl implements MenuItem {
|
||||||
|
private int id;
|
||||||
|
private MenuImpl parent;
|
||||||
|
private String title;
|
||||||
|
SubMenuImpl subMenu;
|
||||||
|
int position; // position in list of visible items, or -1 if not visible
|
||||||
|
|
||||||
|
private MenuItemImpl(int id, MenuImpl parent, String title, SubMenuImpl subMenu) {
|
||||||
|
this.id = id;
|
||||||
|
this.parent = parent;
|
||||||
|
this.position = -1;
|
||||||
|
this.title = title;
|
||||||
|
this.subMenu = subMenu;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setIcon(int iconRes) {
|
public MenuItem setIcon(int iconRes) {
|
||||||
// TODO Auto-generated method stub
|
return this;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setIcon'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setVisible(boolean visible) {
|
public MenuItem setVisible(boolean visible) {
|
||||||
// TODO Auto-generated method stub
|
// GMenu doesn't support invisible items, so we remove them while they're not visible
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setVisible'");
|
if (!visible && isVisible()) {
|
||||||
|
parent.numVisibleItems--;
|
||||||
|
for (int i = parent.items.size()-1; i >= 0; i--) {
|
||||||
|
MenuItemImpl item = parent.items.get(i);
|
||||||
|
if (item != this && item.isVisible())
|
||||||
|
item.position--;
|
||||||
|
else if (item == this)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
native_removeItem(parent.menu, position);
|
||||||
|
position = -1;
|
||||||
|
} else if (visible && !isVisible()) {
|
||||||
|
position = parent.numVisibleItems++;
|
||||||
|
for (int i = parent.items.size()-1; i >= 0; i--) {
|
||||||
|
MenuItemImpl item = parent.items.get(i);
|
||||||
|
if (item != this && item.isVisible())
|
||||||
|
position = item.position++;
|
||||||
|
else if (item == this)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (subMenu != null)
|
||||||
|
native_insertSubmenu(parent.menu, position, title, subMenu.menu);
|
||||||
|
else
|
||||||
|
native_insertItem(parent.menu, position, title, id);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setChecked(boolean checked) {
|
public MenuItem setChecked(boolean checked) {
|
||||||
// TODO Auto-generated method stub
|
return this;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setChecked'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setEnabled(boolean enabled) {
|
public MenuItem setEnabled(boolean enabled) {
|
||||||
// TODO Auto-generated method stub
|
return this;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setEnabled'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setCheckable(boolean checkable) {
|
public MenuItem setCheckable(boolean checkable) {
|
||||||
// TODO Auto-generated method stub
|
return this;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setCheckable'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setTitleCondensed(CharSequence titleCondensed) {
|
public MenuItem setTitleCondensed(CharSequence titleCondensed) {
|
||||||
// TODO Auto-generated method stub
|
return this;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setTitleCondensed'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MenuItem setTitle(CharSequence title) {
|
public MenuItem setTitle(CharSequence title) {
|
||||||
// TODO Auto-generated method stub
|
this.title = String.valueOf(title);
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setTitle'");
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -213,8 +410,7 @@ public class PopupMenu {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVisible() {
|
public boolean isVisible() {
|
||||||
// TODO Auto-generated method stub
|
return position >= 0;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'isVisible'");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -225,73 +421,8 @@ public class PopupMenu {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubMenu getSubMenu() {
|
public SubMenu getSubMenu() {
|
||||||
// TODO Auto-generated method stub
|
return subMenu;
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getSubMenu'");
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inflate a menu resource into this PopupMenu. This is equivalent to
|
|
||||||
* calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
|
|
||||||
*
|
|
||||||
* @param menuRes Menu resource to inflate
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public void inflate(int menuRes) throws Exception {
|
|
||||||
XmlResourceParser parser = mContext.getResources().getXml(menuRes);
|
|
||||||
int type;
|
|
||||||
while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
|
|
||||||
if (type == XmlPullParser.START_TAG && "menu".equals(parser.getName())) {
|
|
||||||
long menu = native_init();
|
|
||||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
|
||||||
if (type == XmlPullParser.START_TAG && "item".equals(parser.getName())) {
|
|
||||||
final TypedArray a = mContext.getResources().obtainAttributes(parser, R.styleable.MenuItem);
|
|
||||||
native_appendItem(menu, a.getString(R.styleable.MenuItem_title), a.getResourceId(R.styleable.MenuItem_id, -1));
|
|
||||||
a.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
popover = native_buildPopover(menu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the menu popup anchored to the view specified during construction.
|
|
||||||
*
|
|
||||||
* @see #dismiss()
|
|
||||||
*/
|
|
||||||
public void show() {
|
|
||||||
System.out.println("PopupMenu.show() called");
|
|
||||||
native_show(popover, mAnchor.widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface responsible for receiving menu item click events if the items
|
|
||||||
* themselves do not have individual item click listeners.
|
|
||||||
*/
|
|
||||||
public interface OnMenuItemClickListener {
|
|
||||||
/**
|
|
||||||
* This method will be invoked when a menu item is clicked if the item
|
|
||||||
* itself did not already handle the event.
|
|
||||||
*
|
|
||||||
* @param item the menu item that was clicked
|
|
||||||
* @return {@code true} if the event was handled, {@code false}
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
boolean onMenuItemClick(MenuItem item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback interface used to notify the application that the menu has closed.
|
|
||||||
*/
|
|
||||||
public interface OnDismissListener {
|
|
||||||
/**
|
|
||||||
* Called when the associated menu has been dismissed.
|
|
||||||
*
|
|
||||||
* @param menu the popup menu that was dismissed
|
|
||||||
*/
|
|
||||||
void onDismiss(PopupMenu menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user