Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
androidjnimenu.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "androidjnimain.h"
5#include "androidjnimenu.h"
9
10#include <QMutex>
11#include <QPoint>
12#include <QQueue>
13#include <QRect>
14#include <QSet>
15#include <QWindow>
16#include <QtCore/private/qjnihelpers_p.h>
17#include <QtCore/QJniObject>
18
20
21using namespace QtAndroid;
22
24{
28
31 static QWindow *activeTopLevelWindow = nullptr;
32 Q_CONSTINIT static QRecursiveMutex menuBarMutex;
33
34 static jmethodID openContextMenuMethodID = 0;
35
36 static jmethodID clearMenuMethodID = 0;
37 static jmethodID addMenuItemMethodID = 0;
38 static int menuNoneValue = 0;
40
41 static jmethodID setCheckableMenuItemMethodID = 0;
42 static jmethodID setCheckedMenuItemMethodID = 0;
43 static jmethodID setEnabledMenuItemMethodID = 0;
44 static jmethodID setIconMenuItemMethodID = 0;
45 static jmethodID setVisibleMenuItemMethodID = 0;
46
48 {
49 QJniObject::callStaticMethod<void>(applicationClass(), "resetOptionsMenu");
50 }
51
53 {
54 QJniObject::callStaticMethod<void>(applicationClass(), "openOptionsMenu");
55 }
56
57 void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env)
58 {
60 if (visibleMenu)
64 env->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID, anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height());
65 }
66
68 {
70 if (visibleMenu == menu) {
71 QJniObject::callStaticMethod<void>(applicationClass(), "closeContextMenu");
73 } else {
75 }
76 }
77
78 // FIXME
80 {
81// QMutexLocker lock(&visibleMenuMutex);
82// if (visibleMenu == menu)
83// {
84// hideContextMenu(menu);
85// showContextMenu(menu);
86// }
87 }
88
90 {
92 if (visibleMenu == menu)
93 visibleMenu = 0;
94 }
95
97 {
100 resetMenuBar();
101 }
102 }
103
105 {
106 Qt::WindowFlags flags = window ? window->flags() : Qt::WindowFlags();
107 if (!window)
108 return;
109
110 bool isNonRegularWindow = flags & (Qt::Desktop | Qt::Popup | Qt::Dialog | Qt::Sheet) & ~Qt::Window;
111 if (isNonRegularWindow)
112 return;
113
116 return;
117
118 visibleMenuBar = 0;
120 for (QAndroidPlatformMenuBar *menuBar : std::as_const(menuBars)) {
121 if (menuBar->parentWindow() == window) {
123 resetMenuBar();
124 break;
125 }
126 }
127
128 }
129
131 {
133 menuBars.insert(menuBar);
134 }
135
137 {
139 menuBars.remove(menuBar);
140 if (visibleMenuBar == menuBar) {
141 visibleMenuBar = 0;
142 resetMenuBar();
143 }
144 }
145
147 {
148 qsizetype i = 0;
149 while (i < s.size()) {
150 ++i;
151 if (s.at(i - 1) != u'&')
152 continue;
153 if (i < s.size() && s.at(i) == u'&')
154 ++i;
155 s.remove(i-1,1);
156 }
157 return s.trimmed();
158 }
159
160 static void fillMenuItem(JNIEnv *env, jobject menuItem, bool checkable, bool checked, bool enabled, bool visible, const QIcon &icon=QIcon())
161 {
162 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setCheckableMenuItemMethodID, checkable));
163 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setCheckedMenuItemMethodID, checked));
164 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setEnabledMenuItemMethodID, enabled));
165
166 if (!icon.isNull()) { // isNull() only checks the d pointer, not the actual image data.
167 int sz = qMax(36, qEnvironmentVariableIntValue("QT_ANDROID_APP_ICON_SIZE"));
168 QImage img = icon.pixmap(QSize(sz,sz),
169 enabled
172 QIcon::On).toImage();
173 if (!img.isNull()) { // Make sure we have a valid image.
174 env->DeleteLocalRef(env->CallObjectMethod(menuItem,
177 }
178 }
179
180 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setVisibleMenuItemMethodID, visible));
181 }
182
183 static int addAllMenuItemsToMenu(JNIEnv *env, jobject menu, QAndroidPlatformMenu *platformMenu) {
184 int order = 0;
185 QMutexLocker lock(platformMenu->menuItemsMutex());
186 const auto items = platformMenu->menuItems();
188 if (item->isSeparator())
189 continue;
190 QString itemText = removeAmpersandEscapes(item->text());
191 jstring jtext = env->NewString(reinterpret_cast<const jchar *>(itemText.data()),
192 itemText.length());
193 jint menuId = platformMenu->menuId(item);
194 jobject menuItem = env->CallObjectMethod(menu,
197 menuId,
198 order++,
199 jtext);
200 env->DeleteLocalRef(jtext);
201 fillMenuItem(env,
202 menuItem,
203 item->isCheckable(),
204 item->isChecked(),
205 item->isEnabled(),
206 item->isVisible(),
207 item->icon());
208 env->DeleteLocalRef(menuItem);
209 }
210
211 return order;
212 }
213
214 static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
215 {
216 env->CallVoidMethod(menu, clearMenuMethodID);
218 if (!visibleMenuBar)
219 return JNI_FALSE;
220
222 int order = 0;
223 QMutexLocker lockMenuBarMutex(visibleMenuBar->menusListMutex());
224 if (menus.size() == 1) { // Expand the menu
225 order = addAllMenuItemsToMenu(env, menu, static_cast<QAndroidPlatformMenu *>(menus.front()));
226 } else {
227 for (QAndroidPlatformMenu *item : menus) {
228 QString itemText = removeAmpersandEscapes(item->text());
229 jstring jtext = env->NewString(reinterpret_cast<const jchar *>(itemText.data()),
230 itemText.length());
231 jint menuId = visibleMenuBar->menuId(item);
232 jobject menuItem = env->CallObjectMethod(menu,
235 menuId,
236 order++,
237 jtext);
238 env->DeleteLocalRef(jtext);
239
240 fillMenuItem(env,
241 menuItem,
242 false,
243 false,
244 item->isEnabled(),
245 item->isVisible(),
246 item->icon());
247 }
248 }
249 return order ? JNI_TRUE : JNI_FALSE;
250 }
251
252 static jboolean onOptionsItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
253 {
255 if (!visibleMenuBar)
256 return JNI_FALSE;
257
259 if (menus.size() == 1) { // Expanded menu
261 if (item) {
262 if (item->menu()) {
263 showContextMenu(item->menu(), QRect(), env);
264 } else {
265 if (item->isCheckable())
266 item->setChecked(checked);
267 item->activated();
268 }
269 }
270 } else {
272 if (menu)
273 showContextMenu(menu, QRect(), env);
274 }
275
276 return JNI_TRUE;
277 }
278
279 static void onOptionsMenuClosed(JNIEnv */*env*/, jobject /*thiz*/, jobject /*menu*/)
280 {
281 }
282
283 static void onCreateContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
284 {
285 env->CallVoidMethod(menu, clearMenuMethodID);
287 if (!visibleMenu)
288 return;
289
291 jstring jtext = env->NewString(reinterpret_cast<const jchar*>(menuText.data()),
292 menuText.length());
293 env->CallObjectMethod(menu, setHeaderTitleContextMenuMethodID, jtext);
294 env->DeleteLocalRef(jtext);
296 }
297
298 static void fillContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
299 {
300 env->CallVoidMethod(menu, clearMenuMethodID);
302 if (!visibleMenu)
303 return;
304
306 }
307
308 static jboolean onContextItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
309 {
312 if (item) {
313 if (item->menu()) {
314 showContextMenu(item->menu(), QRect(), env);
315 } else {
316 if (item->isCheckable())
317 item->setChecked(checked);
318 item->activated();
320 visibleMenu = 0;
321 for (QAndroidPlatformMenu *menu : std::as_const(pendingContextMenus)) {
322 if (menu->isVisible())
323 menu->aboutToHide();
324 }
326 }
327 }
328 return JNI_TRUE;
329 }
330
331 static void onContextMenuClosed(JNIEnv *env, jobject /*thiz*/, jobject /*menu*/)
332 {
334 if (!visibleMenu)
335 return;
336
338 visibleMenu = 0;
341 }
342
343 static JNINativeMethod methods[] = {
344 {"onPrepareOptionsMenu", "(Landroid/view/Menu;)Z", (void *)onPrepareOptionsMenu},
345 {"onOptionsItemSelected", "(IZ)Z", (void *)onOptionsItemSelected},
346 {"onOptionsMenuClosed", "(Landroid/view/Menu;)V", (void*)onOptionsMenuClosed},
347 {"onCreateContextMenu", "(Landroid/view/ContextMenu;)V", (void *)onCreateContextMenu},
348 {"fillContextMenu", "(Landroid/view/Menu;)V", (void *)fillContextMenu},
349 {"onContextItemSelected", "(IZ)Z", (void *)onContextItemSelected},
350 {"onContextMenuClosed", "(Landroid/view/Menu;)V", (void*)onContextMenuClosed},
351 };
352
353#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
354 clazz = env->FindClass(CLASS_NAME); \
355 if (!clazz) { \
356 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), classErrorMsgFmt(), CLASS_NAME); \
357 return false; \
358 }
359
360#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
361 VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
362 if (!VAR) { \
363 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE); \
364 return false; \
365 }
366
367#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
368 VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
369 if (!VAR) { \
370 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE); \
371 return false; \
372 }
373
374#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
375 VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
376 if (!VAR) { \
377 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), FIELD_NAME, FIELD_SIGNATURE); \
378 return false; \
379 }
380
381 bool registerNatives(JNIEnv *env)
382 {
383 jclass appClass = applicationClass();
384
385 if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
386 __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
387 return false;
388 }
389
390 GET_AND_CHECK_STATIC_METHOD(openContextMenuMethodID, appClass, "openContextMenu", "(IIII)V");
391
392 jclass clazz;
393 FIND_AND_CHECK_CLASS("android/view/Menu");
394 GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V");
395 GET_AND_CHECK_METHOD(addMenuItemMethodID, clazz, "add", "(IIILjava/lang/CharSequence;)Landroid/view/MenuItem;");
396 jfieldID menuNoneFiledId;
397 GET_AND_CHECK_STATIC_FIELD(menuNoneFiledId, clazz, "NONE", "I");
398 menuNoneValue = env->GetStaticIntField(clazz, menuNoneFiledId);
399
400 FIND_AND_CHECK_CLASS("android/view/ContextMenu");
401 GET_AND_CHECK_METHOD(setHeaderTitleContextMenuMethodID, clazz, "setHeaderTitle","(Ljava/lang/CharSequence;)Landroid/view/ContextMenu;");
402
403 FIND_AND_CHECK_CLASS("android/view/MenuItem");
404 GET_AND_CHECK_METHOD(setCheckableMenuItemMethodID, clazz, "setCheckable", "(Z)Landroid/view/MenuItem;");
405 GET_AND_CHECK_METHOD(setCheckedMenuItemMethodID, clazz, "setChecked", "(Z)Landroid/view/MenuItem;");
406 GET_AND_CHECK_METHOD(setEnabledMenuItemMethodID, clazz, "setEnabled", "(Z)Landroid/view/MenuItem;");
407 GET_AND_CHECK_METHOD(setIconMenuItemMethodID, clazz, "setIcon", "(Landroid/graphics/drawable/Drawable;)Landroid/view/MenuItem;");
408 GET_AND_CHECK_METHOD(setVisibleMenuItemMethodID, clazz, "setVisible", "(Z)Landroid/view/MenuItem;");
409 return true;
410 }
411}
412
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
#define FIND_AND_CHECK_CLASS(CLASS_NAME)
#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE)
#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
PlatformMenusType menus() const
int menuId(QPlatformMenu *menu) const
QPlatformMenu * menuForId(int menuId) const
QPlatformMenuItem * menuItemForId(int menuId) const
bool isEnabled() const
Returns true if the item is enabled; otherwise, false is returned.
bool isVisible() const
Returns true if the item is visible; otherwise, false is returned.
The QIcon class provides scalable icons in different modes and states.
Definition qicon.h:20
bool isNull() const
Returns true if the icon is empty; otherwise returns false.
Definition qicon.cpp:973
@ Disabled
Definition qicon.h:22
@ Normal
Definition qicon.h:22
@ On
Definition qicon.h:23
QPixmap pixmap(const QSize &size, Mode mode=Normal, State state=Off) const
Returns a pixmap with the requested size, mode, and state, generating one if necessary.
Definition qicon.cpp:788
\inmodule QtGui
Definition qimage.h:37
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool empty() const noexcept
Definition qlist.h:682
bool removeOne(const AT &t)
Definition qlist.h:581
value_type takeLast()
Definition qlist.h:550
reference front()
Definition qlist.h:684
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
void aboutToShow()
This signal is emitted just before the menu is shown to the user.
void aboutToHide()
\inmodule QtCore
Definition qmutex.h:317
void aboutToHide()
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
\inmodule QtCore
Definition qmutex.h:313
Definition qset.h:18
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1095
qsizetype length() const
Returns the number of characters in this string.
Definition qstring.h:187
bool isVisible() const
Definition qwidget.h:874
\inmodule QtGui
Definition qwindow.h:63
static bool registerNatives()
Combined button and popup list for selecting options.
static jmethodID setCheckableMenuItemMethodID
static int addAllMenuItemsToMenu(JNIEnv *env, jobject menu, QAndroidPlatformMenu *platformMenu)
void hideContextMenu(QAndroidPlatformMenu *menu)
static jmethodID addMenuItemMethodID
static jboolean onContextItemSelected(JNIEnv *env, jobject, jint menuId, jboolean checked)
static jmethodID setVisibleMenuItemMethodID
static Q_CONSTINIT QRecursiveMutex menuBarMutex
static QAndroidPlatformMenu * visibleMenu
static QString removeAmpersandEscapes(QString s)
static void onCreateContextMenu(JNIEnv *env, jobject, jobject menu)
static jmethodID setHeaderTitleContextMenuMethodID
static Q_CONSTINIT QRecursiveMutex visibleMenuMutex
void setActiveTopLevelWindow(QWindow *window)
void removeMenuBar(QAndroidPlatformMenuBar *menuBar)
static QWindow * activeTopLevelWindow
void syncMenu(QAndroidPlatformMenu *)
void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu)
static void fillContextMenu(JNIEnv *env, jobject, jobject menu)
static void onOptionsMenuClosed(JNIEnv *, jobject, jobject)
static jmethodID setEnabledMenuItemMethodID
static jmethodID clearMenuMethodID
static JNINativeMethod methods[]
void addMenuBar(QAndroidPlatformMenuBar *menuBar)
static QList< QAndroidPlatformMenu * > pendingContextMenus
static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject, jobject menu)
static jmethodID openContextMenuMethodID
static void onContextMenuClosed(JNIEnv *env, jobject, jobject)
void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env)
static jmethodID setCheckedMenuItemMethodID
static QSet< QAndroidPlatformMenuBar * > menuBars
static int menuNoneValue
static jboolean onOptionsItemSelected(JNIEnv *env, jobject, jint menuId, jboolean checked)
static void fillMenuItem(JNIEnv *env, jobject menuItem, bool checkable, bool checked, bool enabled, bool visible, const QIcon &icon=QIcon())
static jmethodID setIconMenuItemMethodID
void setMenuBar(QAndroidPlatformMenuBar *menuBar, QWindow *window)
static QAndroidPlatformMenuBar * visibleMenuBar
jobject createBitmap(QImage img, JNIEnv *env)
jclass applicationClass()
jobject createBitmapDrawable(jobject bitmap, JNIEnv *env)
@ Desktop
Definition qnamespace.h:214
@ Popup
Definition qnamespace.h:210
@ Window
Definition qnamespace.h:206
@ Dialog
Definition qnamespace.h:207
@ Sheet
Definition qnamespace.h:208
NSMenu QCocoaMenu * platformMenu
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLbitfield flags
GLint void * img
Definition qopenglext.h:233
GLdouble s
[6]
Definition qopenglext.h:235
GLfixed GLfixed GLint GLint order
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
ptrdiff_t qsizetype
Definition qtypes.h:70
QReadWriteLock lock
[0]
QGraphicsItem * item
QList< QTreeWidgetItem * > items
aWidget window() -> setWindowTitle("New Window Title")
[2]
QMenu menu
[5]
QMenuBar * menuBar
[0]