diff options
author | Paul Olav Tvete <paul.tvete@digia.com> | 2013-03-04 10:16:42 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-03-05 08:31:23 +0100 |
commit | 97fcf3bc987a18f32233fea550eb4a5a22e2b822 (patch) | |
tree | 31d75557fdc4a525af8f5053db514066c63bd1bd /src/plugins/platforms/android/src/androidjnimenu.cpp | |
parent | 1b582d64eb6d13e60a02ebc40956302a4864eb6c (diff) |
Introducing the Qt Android port
Based on the Necessitas project by Bogdan Vatra.
Contributors to the Qt5 project:
BogDan Vatra <bogdan@kde.org>
Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
hjk <hjk121@nokiamail.com>
Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Paul Olav Tvete <paul.tvete@digia.com>
Robin Burchell <robin+qt@viroteck.net>
Samuel Rødal <samuel.rodal@digia.com>
Yoann Lopes <yoann.lopes@digia.com>
The full history of the Qt5 port can be found in refs/old-heads/android,
SHA-1 249ca9ca2c7d876b91b31df9434dde47f9065d0d
Change-Id: Iff1a7b2dbb707c986f2639e65e39ed8f22430120
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'src/plugins/platforms/android/src/androidjnimenu.cpp')
-rw-r--r-- | src/plugins/platforms/android/src/androidjnimenu.cpp | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/src/plugins/platforms/android/src/androidjnimenu.cpp b/src/plugins/platforms/android/src/androidjnimenu.cpp new file mode 100644 index 0000000000..e49af0fdac --- /dev/null +++ b/src/plugins/platforms/android/src/androidjnimenu.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> +** 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 <qmutex.h> +#include <qset.h> +#include <qqueue.h> +#include <android/log.h> +#include "qandroidplatformmenubar.h" +#include "qandroidplatformmenu.h" +#include <qandroidplatformmenuitem.h> + +using namespace QtAndroid; + +namespace QtAndroidMenu +{ + static QQueue<QAndroidPlatformMenu *> pendingContextMenus; + static QAndroidPlatformMenu *visibleMenu = 0; + static QMutex visibleMenuMutex(QMutex::Recursive); + + static QSet<QAndroidPlatformMenuBar *> 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<const jchar *>(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<QAndroidPlatformMenu *>(menus.front())); + } else { + foreach (QAndroidPlatformMenu *item, menus) { + jstring jtext = env->NewString(reinterpret_cast<const jchar *>(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<QAndroidPlatformMenuItem *>(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<QAndroidPlatformMenu *>(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<const jchar*>(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<QAndroidPlatformMenuItem *>(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; + } +} |