/**************************************************************************** ** ** Copyright (C) 2012 BogDan Vatra ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "androidjnimenu.h" #include "androidjnimain.h" #include #include #include #include #include "qandroidplatformmenubar.h" #include "qandroidplatformmenu.h" #include using namespace QtAndroid; namespace QtAndroidMenu { static QQueue pendingContextMenus; static QAndroidPlatformMenu *visibleMenu = 0; static QMutex visibleMenuMutex(QMutex::Recursive); static QSet menuBars; static QAndroidPlatformMenuBar *visibleMenuBar = 0; static QWindow *activeTopLevelWindow = 0; static QMutex menuBarMutex(QMutex::Recursive); static jmethodID openContextMenuMethodID = 0; static jmethodID closeContextMenuMethodID = 0; static jmethodID resetOptionsMenuMethodID = 0; static jmethodID clearMenuMethodID = 0; static jmethodID addMenuItemMethodID = 0; static int menuNoneValue = 0; static jmethodID setHeaderTitleContextMenuMethodID = 0; static jmethodID setCheckableMenuItemMethodID = 0; static jmethodID setCheckedMenuItemMethodID = 0; static jmethodID setEnabledMenuItemMethodID = 0; static jmethodID setIconMenuItemMethodID = 0; static jmethodID setVisibleMenuItemMethodID = 0; void resetMenuBar() { AttachedJNIEnv env; if (env.jniEnv) env.jniEnv->CallStaticVoidMethod(applicationClass(), resetOptionsMenuMethodID); } void showContextMenu(QAndroidPlatformMenu *menu, JNIEnv *env) { QMutexLocker lock(&visibleMenuMutex); if (visibleMenu) { pendingContextMenus.enqueue(menu); } else { visibleMenu = menu; menu->aboutToShow(); if (env) { env->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID); } else { AttachedJNIEnv aenv; if (aenv.jniEnv) aenv.jniEnv->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID); } } } void hideContextMenu(QAndroidPlatformMenu *menu) { QMutexLocker lock(&visibleMenuMutex); if (visibleMenu == menu) { AttachedJNIEnv env; if (env.jniEnv) env.jniEnv->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID); } else { pendingContextMenus.removeOne(menu); } } void syncMenu(QAndroidPlatformMenu */*menu*/) { // QMutexLocker lock(&visibleMenuMutex); // if (visibleMenu == menu) // { // hideContextMenu(menu); // showContextMenu(menu); // } } void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu) { QMutexLocker lock(&visibleMenuMutex); if (visibleMenu == menu) visibleMenu = 0; } void setMenuBar(QAndroidPlatformMenuBar *menuBar, QWindow *window) { if (activeTopLevelWindow == window && visibleMenuBar != menuBar) { visibleMenuBar = menuBar; resetMenuBar(); } } void setActiveTopLevelWindow(QWindow *window) { QMutexLocker lock(&menuBarMutex); if (activeTopLevelWindow == window) return; visibleMenuBar = 0; activeTopLevelWindow = window; #ifdef ANDROID_PLUGIN_OPENGL //only one toplevel window, so the menu bar always belongs to us if (menuBars.size() == 1) { visibleMenuBar = *menuBars.constBegin(); //since QSet doesn't have first() } else #endif foreach (QAndroidPlatformMenuBar *menuBar, menuBars) { if (menuBar->parentWindow() == window) { visibleMenuBar = menuBar; break; } } resetMenuBar(); } void addMenuBar(QAndroidPlatformMenuBar *menuBar) { QMutexLocker lock(&menuBarMutex); menuBars.insert(menuBar); } void removeMenuBar(QAndroidPlatformMenuBar *menuBar) { QMutexLocker lock(&menuBarMutex); menuBars.remove(menuBar); if (visibleMenuBar == menuBar) resetMenuBar(); } static void fillMenuItem(JNIEnv *env, jobject menuItem, bool checkable, bool checked, bool enabled, bool visible, const QIcon &icon=QIcon()) { env->CallObjectMethod(menuItem, setCheckableMenuItemMethodID, checkable); env->CallObjectMethod(menuItem, setCheckedMenuItemMethodID, checked); env->CallObjectMethod(menuItem, setEnabledMenuItemMethodID, enabled); if (!icon.isNull()) { int sz = qMax(36, qgetenv("QT_ANDROID_APP_ICON_SIZE").toInt()); QImage img = icon.pixmap(QSize(sz,sz), enabled ? QIcon::Normal : QIcon::Disabled, QIcon::On).toImage(); env->CallObjectMethod(menuItem, setIconMenuItemMethodID, createBitmapDrawable(createBitmap(img, env), env)); } env->CallObjectMethod(menuItem, setVisibleMenuItemMethodID, visible); } static int addAllMenuItemsToMenu(JNIEnv *env, jobject menu, QAndroidPlatformMenu *platformMenu) { int order = 0; QMutexLocker lock(platformMenu->menuItemsMutex()); foreach (QAndroidPlatformMenuItem *item, platformMenu->menuItems()) { if (item->isSeparator()) continue; jstring jtext = env->NewString(reinterpret_cast(item->text().data()), item->text().length()); jobject menuItem = env->CallObjectMethod(menu, addMenuItemMethodID, menuNoneValue, int(item->tag()), order++, jtext); env->DeleteLocalRef(jtext); fillMenuItem(env, menuItem, item->isCheckable(), item->isChecked(), item->isEnabled(), item->isVisible(), item->icon()); } return order; } static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject /*thiz*/, jobject menu) { env->CallVoidMethod(menu, clearMenuMethodID); QMutexLocker lock(&menuBarMutex); if (!visibleMenuBar) return JNI_FALSE; const QAndroidPlatformMenuBar::PlatformMenusType &menus = visibleMenuBar->menus(); int order = 0; QMutexLocker lockMenuBarMutex(visibleMenuBar->menusListMutex()); if (menus.size() == 1) { // Expand the menu order = addAllMenuItemsToMenu(env, menu, static_cast(menus.front())); } else { foreach (QAndroidPlatformMenu *item, menus) { jstring jtext = env->NewString(reinterpret_cast(item->text().data()), item->text().length()); jobject menuItem = env->CallObjectMethod(menu, addMenuItemMethodID, menuNoneValue, int(item->tag()), order++, jtext); env->DeleteLocalRef(jtext); fillMenuItem(env, menuItem, false, false, item->isEnabled(), item->isVisible(), item->icon()); } } return order ? JNI_TRUE : JNI_FALSE; } static jboolean onOptionsItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked) { QMutexLocker lock(&menuBarMutex); if (!visibleMenuBar) return JNI_FALSE; const QAndroidPlatformMenuBar::PlatformMenusType &menus = visibleMenuBar->menus(); if (menus.size() == 1) { // Expanded menu QAndroidPlatformMenuItem *item = static_cast(menus.front()->menuItemForTag(menuId)); if (item) { if (item->menu()) { showContextMenu(item->menu(), env); } else { if (item->isCheckable()) item->setChecked(checked); item->activated(); } } } else { QAndroidPlatformMenu *menu = static_cast(visibleMenuBar->menuForTag(menuId)); if (menu) showContextMenu(menu, env); } return JNI_TRUE; } static void onOptionsMenuClosed(JNIEnv */*env*/, jobject /*thiz*/, jobject /*menu*/) { } static void onCreateContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu) { env->CallVoidMethod(menu, clearMenuMethodID); QMutexLocker lock(&visibleMenuMutex); if (!visibleMenu) return; jstring jtext = env->NewString(reinterpret_cast(visibleMenu->text().data()), visibleMenu->text().length()); env->CallObjectMethod(menu, setHeaderTitleContextMenuMethodID, jtext); env->DeleteLocalRef(jtext); addAllMenuItemsToMenu(env, menu, visibleMenu); } static jboolean onContextItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked) { QMutexLocker lock(&visibleMenuMutex); QAndroidPlatformMenuItem * item = static_cast(visibleMenu->menuItemForTag(menuId)); if (item) { if (item->menu()) { showContextMenu(item->menu(), env); } else { if (item->isCheckable()) item->setChecked(checked); item->activated(); } } return JNI_TRUE; } static void onContextMenuClosed(JNIEnv *env, jobject /*thiz*/, jobject /*menu*/) { QMutexLocker lock(&visibleMenuMutex); if (!visibleMenu) return; visibleMenu->aboutToHide(); visibleMenu = 0; if (!pendingContextMenus.empty()) showContextMenu(pendingContextMenus.dequeue(), env); } static JNINativeMethod methods[] = { {"onPrepareOptionsMenu", "(Landroid/view/Menu;)Z", (void *)onPrepareOptionsMenu}, {"onOptionsItemSelected", "(IZ)Z", (void *)onOptionsItemSelected}, {"onOptionsMenuClosed", "(Landroid/view/Menu;)V", (void*)onOptionsMenuClosed}, {"onCreateContextMenu", "(Landroid/view/ContextMenu;)V", (void *)onCreateContextMenu}, {"onContextItemSelected", "(IZ)Z", (void *)onContextItemSelected}, {"onContextMenuClosed", "(Landroid/view/Menu;)V", (void*)onContextMenuClosed}, }; #define FIND_AND_CHECK_CLASS(CLASS_NAME) \ clazz = env->FindClass(CLASS_NAME); \ if (!clazz) { \ __android_log_print(ANDROID_LOG_FATAL, qtTagText(), classErrorMsgFmt(), CLASS_NAME); \ return false; \ } #define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \ VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \ if (!VAR) { \ __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE); \ return false; \ } #define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \ VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \ if (!VAR) { \ __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE); \ return false; \ } #define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \ VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \ if (!VAR) { \ __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), FIELD_NAME, FIELD_SIGNATURE); \ return false; \ } bool registerNatives(JNIEnv *env) { jclass appClass = applicationClass(); if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed"); return false; } GET_AND_CHECK_STATIC_METHOD(openContextMenuMethodID, appClass, "openContextMenu", "()V"); GET_AND_CHECK_STATIC_METHOD(closeContextMenuMethodID, appClass, "closeContextMenu", "()V"); GET_AND_CHECK_STATIC_METHOD(resetOptionsMenuMethodID, appClass, "resetOptionsMenu", "()V"); jclass clazz; FIND_AND_CHECK_CLASS("android/view/Menu"); GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V"); GET_AND_CHECK_METHOD(addMenuItemMethodID, clazz, "add", "(IIILjava/lang/CharSequence;)Landroid/view/MenuItem;"); jfieldID menuNoneFiledId; GET_AND_CHECK_STATIC_FIELD(menuNoneFiledId, clazz, "NONE", "I"); menuNoneValue = env->GetStaticIntField(clazz, menuNoneFiledId); FIND_AND_CHECK_CLASS("android/view/ContextMenu"); GET_AND_CHECK_METHOD(setHeaderTitleContextMenuMethodID, clazz, "setHeaderTitle","(Ljava/lang/CharSequence;)Landroid/view/ContextMenu;"); FIND_AND_CHECK_CLASS("android/view/MenuItem"); GET_AND_CHECK_METHOD(setCheckableMenuItemMethodID, clazz, "setCheckable", "(Z)Landroid/view/MenuItem;"); GET_AND_CHECK_METHOD(setCheckedMenuItemMethodID, clazz, "setChecked", "(Z)Landroid/view/MenuItem;"); GET_AND_CHECK_METHOD(setEnabledMenuItemMethodID, clazz, "setEnabled", "(Z)Landroid/view/MenuItem;"); GET_AND_CHECK_METHOD(setIconMenuItemMethodID, clazz, "setIcon", "(Landroid/graphics/drawable/Drawable;)Landroid/view/MenuItem;"); GET_AND_CHECK_METHOD(setVisibleMenuItemMethodID, clazz, "setVisible", "(Z)Landroid/view/MenuItem;"); return true; } }