Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:critical reason:data-parser
4
10
11#include <QMutex>
12#include <QPoint>
13#include <QQueue>
14#include <QRect>
15#include <QSet>
16#include <QWindow>
17#include <QtCore/private/qjnihelpers_p.h>
18#include <QtCore/QJniObject>
19
21
22using namespace QtAndroid;
23
24Q_DECLARE_JNI_CLASS(QtMenuInterface, "org/qtproject/qt/android/QtMenuInterface");
25
27{
30 Q_CONSTINIT static QRecursiveMutex visibleMenuMutex;
31
34 static QWindow *activeTopLevelWindow = nullptr;
35 Q_CONSTINIT static QRecursiveMutex menuBarMutex;
36
39 static int menuNoneValue = 0;
41
47
49 {
51 reg->callInterface<QtJniTypes::QtMenuInterface>("resetOptionsMenu");
52 }
53
55 {
57 reg->callInterface<QtJniTypes::QtMenuInterface>("openOptionsMenu");
58 }
59
60 void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect)
61 {
62 QMutexLocker lock(&visibleMenuMutex);
63 if (visibleMenu)
65 visibleMenu = menu;
66 menu->aboutToShow();
68 reg->callInterface<QtJniTypes::QtMenuInterface, void>("openContextMenu", anchorRect.x(),
69 anchorRect.y(), anchorRect.width(),
70 anchorRect.height());
71 }
72
74 {
75 QMutexLocker lock(&visibleMenuMutex);
76 if (visibleMenu == menu) {
78 reg->callInterface<QtJniTypes::QtMenuInterface>("closeContextMenu");
79 pendingContextMenus.clear();
80 } else {
81 pendingContextMenus.removeOne(menu);
82 }
83 }
84
85 // FIXME
87 {
88// QMutexLocker lock(&visibleMenuMutex);
89// if (visibleMenu == menu)
90// {
91// hideContextMenu(menu);
92// showContextMenu(menu);
93// }
94 }
95
97 {
98 QMutexLocker lock(&visibleMenuMutex);
99 if (visibleMenu == menu)
100 visibleMenu = 0;
101 }
102
103 void setMenuBar(QAndroidPlatformMenuBar *menuBar, QWindow *window)
104 {
105 if (activeTopLevelWindow == window && visibleMenuBar != menuBar) {
106 visibleMenuBar = menuBar;
108 }
109 }
110
111 void setActiveTopLevelWindow(QWindow *window)
112 {
113 Qt::WindowFlags flags = window ? window->flags() : Qt::WindowFlags();
114 if (!window)
115 return;
116
117 bool isNonRegularWindow = flags & (Qt::Popup | Qt::Dialog | Qt::Sheet) & ~Qt::Window;
118 if (isNonRegularWindow)
119 return;
120
121 QMutexLocker lock(&menuBarMutex);
122 if (activeTopLevelWindow == window)
123 return;
124
125 visibleMenuBar = 0;
126 activeTopLevelWindow = window;
127 for (QAndroidPlatformMenuBar *menuBar : std::as_const(menuBars)) {
128 if (menuBar->parentWindow() == window) {
129 visibleMenuBar = menuBar;
130 resetMenuBar();
131 break;
132 }
133 }
134
135 }
136
138 {
139 QMutexLocker lock(&menuBarMutex);
140 menuBars.insert(menuBar);
141 }
142
144 {
145 QMutexLocker lock(&menuBarMutex);
146 menuBars.remove(menuBar);
147 if (visibleMenuBar == menuBar) {
148 visibleMenuBar = 0;
150 }
151 }
152
153 static QString removeAmpersandEscapes(QString s)
154 {
155 qsizetype i = 0;
156 while (i < s.size()) {
157 ++i;
158 if (s.at(i - 1) != u'&')
159 continue;
160 if (i < s.size() && s.at(i) == u'&')
161 ++i;
162 s.remove(i-1,1);
163 }
164 return s.trimmed();
165 }
166
167 static void fillMenuItem(JNIEnv *env, jobject menuItem, bool checkable, bool checked, bool enabled, bool visible, const QIcon &icon=QIcon())
168 {
169 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setCheckableMenuItemMethodID, checkable));
170 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setCheckedMenuItemMethodID, checked));
171 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setEnabledMenuItemMethodID, enabled));
172
173 if (!icon.isNull()) { // isNull() only checks the d pointer, not the actual image data.
174 int sz = qMax(36, qEnvironmentVariableIntValue("QT_ANDROID_APP_ICON_SIZE"));
175 QImage img = icon.pixmap(QSize(sz,sz),
176 enabled
177 ? QIcon::Normal
178 : QIcon::Disabled,
179 QIcon::On).toImage();
180 if (!img.isNull()) { // Make sure we have a valid image.
181 env->DeleteLocalRef(env->CallObjectMethod(menuItem,
182 setIconMenuItemMethodID,
183 createBitmapDrawable(createBitmap(img, env), env)));
184 }
185 }
186
187 env->DeleteLocalRef(env->CallObjectMethod(menuItem, setVisibleMenuItemMethodID, visible));
188 }
189
190 static int addAllMenuItemsToMenu(JNIEnv *env, jobject menu, QAndroidPlatformMenu *platformMenu) {
191 int order = 0;
192 QMutexLocker lock(platformMenu->menuItemsMutex());
193 const auto items = platformMenu->menuItems();
194 for (QAndroidPlatformMenuItem *item : items) {
195 if (item->isSeparator())
196 continue;
197 QString itemText = removeAmpersandEscapes(item->text());
198 jstring jtext = env->NewString(reinterpret_cast<const jchar *>(itemText.data()),
199 itemText.length());
200 jint menuId = platformMenu->menuId(item);
201 jobject menuItem = env->CallObjectMethod(menu,
202 addMenuItemMethodID,
203 menuNoneValue,
204 menuId,
205 order++,
206 jtext);
207 env->DeleteLocalRef(jtext);
208 fillMenuItem(env,
209 menuItem,
210 item->isCheckable(),
211 item->isChecked(),
212 item->isEnabled(),
213 item->isVisible(),
214 item->icon());
215 env->DeleteLocalRef(menuItem);
216 }
217
218 return order;
219 }
220
221 static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject thiz, jobject menu)
222 {
223 Q_UNUSED(thiz)
224
225 env->CallVoidMethod(menu, clearMenuMethodID);
226 QMutexLocker lock(&menuBarMutex);
227 if (!visibleMenuBar)
228 return JNI_FALSE;
229
231 int order = 0;
232 QMutexLocker lockMenuBarMutex(visibleMenuBar->menusListMutex());
233 if (menus.size() == 1) { // Expand the menu
234 order = addAllMenuItemsToMenu(env, menu, static_cast<QAndroidPlatformMenu *>(menus.front()));
235 } else {
236 for (QAndroidPlatformMenu *item : menus) {
237 QString itemText = removeAmpersandEscapes(item->text());
238 jstring jtext = env->NewString(reinterpret_cast<const jchar *>(itemText.data()),
239 itemText.length());
240 jint menuId = visibleMenuBar->menuId(item);
241 jobject menuItem = env->CallObjectMethod(menu,
242 addMenuItemMethodID,
243 menuNoneValue,
244 menuId,
245 order++,
246 jtext);
247 env->DeleteLocalRef(jtext);
248
249 fillMenuItem(env,
250 menuItem,
251 false,
252 false,
253 item->isEnabled(),
254 item->isVisible(),
255 item->icon());
256 }
257 }
258 return order ? JNI_TRUE : JNI_FALSE;
259 }
260
261 static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
262 {
263 Q_UNUSED(env)
264 Q_UNUSED(thiz)
265
266 QMutexLocker lock(&menuBarMutex);
267 if (!visibleMenuBar)
268 return JNI_FALSE;
269
271 if (menus.size() == 1) { // Expanded menu
272 QAndroidPlatformMenuItem *item = static_cast<QAndroidPlatformMenuItem *>(menus.front()->menuItemForId(menuId));
273 if (item) {
274 if (item->menu()) {
275 showContextMenu(item->menu(), QRect());
276 } else {
277 if (item->isCheckable())
278 item->setChecked(checked);
279 item->activated();
280 }
281 }
282 } else {
283 QAndroidPlatformMenu *menu = static_cast<QAndroidPlatformMenu *>(visibleMenuBar->menuForId(menuId));
284 if (menu)
285 showContextMenu(menu, QRect());
286 }
287
288 return JNI_TRUE;
289 }
290
291 static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
292 {
293 Q_UNUSED(env)
294 Q_UNUSED(thiz)
295 Q_UNUSED(menu)
296 }
297
298 static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
299 {
300 Q_UNUSED(thiz)
301
302 env->CallVoidMethod(menu, clearMenuMethodID);
303 QMutexLocker lock(&visibleMenuMutex);
304 if (!visibleMenu)
305 return;
306
307 QString menuText = removeAmpersandEscapes(visibleMenu->text());
308 jstring jtext = env->NewString(reinterpret_cast<const jchar*>(menuText.data()),
309 menuText.length());
310 env->CallObjectMethod(menu, setHeaderTitleContextMenuMethodID, jtext);
311 env->DeleteLocalRef(jtext);
312 addAllMenuItemsToMenu(env, menu, visibleMenu);
313 }
314
315 static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
316 {
317 Q_UNUSED(thiz)
318 env->CallVoidMethod(menu, clearMenuMethodID);
319 QMutexLocker lock(&visibleMenuMutex);
320 if (!visibleMenu)
321 return;
322
323 addAllMenuItemsToMenu(env, menu, visibleMenu);
324 }
325
326 static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
327 {
328 Q_UNUSED(env)
329 Q_UNUSED(thiz)
330
331 QMutexLocker lock(&visibleMenuMutex);
332 QAndroidPlatformMenuItem * item = static_cast<QAndroidPlatformMenuItem *>(visibleMenu->menuItemForId(menuId));
333 if (item) {
334 if (item->menu()) {
335 showContextMenu(item->menu(), QRect());
336 } else {
337 if (item->isCheckable())
338 item->setChecked(checked);
339 item->activated();
340 visibleMenu->aboutToHide();
341 visibleMenu = 0;
342 for (QAndroidPlatformMenu *menu : std::as_const(pendingContextMenus)) {
343 if (menu->isVisible())
344 menu->aboutToHide();
345 }
346 pendingContextMenus.clear();
347 }
348 }
349 return JNI_TRUE;
350 }
351
352 static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
353 {
354 Q_UNUSED(env)
355 Q_UNUSED(thiz)
356 Q_UNUSED(menu)
357
358 QMutexLocker lock(&visibleMenuMutex);
359 if (!visibleMenu)
360 return;
361
362 visibleMenu->aboutToHide();
363 visibleMenu = 0;
364 if (!pendingContextMenus.empty())
365 showContextMenu(pendingContextMenus.takeLast(), QRect());
366 }
367
369 {"onPrepareOptionsMenu", "(Landroid/view/Menu;)Z", (void *)onPrepareOptionsMenu},
370 {"onOptionsItemSelected", "(IZ)Z", (void *)onOptionsItemSelected},
371 {"onOptionsMenuClosed", "(Landroid/view/Menu;)V", (void*)onOptionsMenuClosed},
372 {"onCreateContextMenu", "(Landroid/view/ContextMenu;)V", (void *)onCreateContextMenu},
373 {"fillContextMenu", "(Landroid/view/Menu;)V", (void *)fillContextMenu},
374 {"onContextItemSelected", "(IZ)Z", (void *)onContextItemSelected},
375 {"onContextMenuClosed", "(Landroid/view/Menu;)V", (void*)onContextMenuClosed},
376 };
377
378#define FIND_AND_CHECK_CLASS(CLASS_NAME)
379 clazz = env->FindClass(CLASS_NAME);
380 if (!clazz) {
381 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), classErrorMsgFmt(), CLASS_NAME);
382 return false;
383 }
384
385#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
386 VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE);
387 if (!VAR) {
388 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE);
389 return false;
390 }
391
392#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
393 VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE);
394 if (!VAR) {
395 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE);
396 return false;
397 }
398
399#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE)
400 VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE);
401 if (!VAR) {
402 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), FIELD_NAME, FIELD_SIGNATURE);
403 return false;
404 }
405
406 bool registerNatives(QJniEnvironment &env)
407 {
408 jclass appClass = applicationClass();
409
410 if (!env.registerNativeMethods(appClass, methods, sizeof(methods) / sizeof(methods[0]))) {
411 __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
412 return false;
413 }
414
415 jclass clazz;
416 FIND_AND_CHECK_CLASS("android/view/Menu");
417 GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V");
418 GET_AND_CHECK_METHOD(addMenuItemMethodID, clazz, "add", "(IIILjava/lang/CharSequence;)Landroid/view/MenuItem;");
419 jfieldID menuNoneFiledId;
420 GET_AND_CHECK_STATIC_FIELD(menuNoneFiledId, clazz, "NONE", "I");
421 menuNoneValue = env->GetStaticIntField(clazz, menuNoneFiledId);
422
423 FIND_AND_CHECK_CLASS("android/view/ContextMenu");
424 GET_AND_CHECK_METHOD(setHeaderTitleContextMenuMethodID, clazz, "setHeaderTitle","(Ljava/lang/CharSequence;)Landroid/view/ContextMenu;");
425
426 FIND_AND_CHECK_CLASS("android/view/MenuItem");
427 GET_AND_CHECK_METHOD(setCheckableMenuItemMethodID, clazz, "setCheckable", "(Z)Landroid/view/MenuItem;");
428 GET_AND_CHECK_METHOD(setCheckedMenuItemMethodID, clazz, "setChecked", "(Z)Landroid/view/MenuItem;");
429 GET_AND_CHECK_METHOD(setEnabledMenuItemMethodID, clazz, "setEnabled", "(Z)Landroid/view/MenuItem;");
430 GET_AND_CHECK_METHOD(setIconMenuItemMethodID, clazz, "setIcon", "(Landroid/graphics/drawable/Drawable;)Landroid/view/MenuItem;");
431 GET_AND_CHECK_METHOD(setVisibleMenuItemMethodID, clazz, "setVisible", "(Z)Landroid/view/MenuItem;");
432 return true;
433 }
434}
435
436QT_END_NAMESPACE
#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE)
#define FIND_AND_CHECK_CLASS(CLASS_NAME)
#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
PlatformMenusType menus() const
QList< QAndroidPlatformMenu * > PlatformMenusType
QAndroidPlatformMenu * menu() const
void setChecked(bool isChecked) override
PlatformMenuItemsType menuItems() const
Definition qlist.h:80
\inmodule QtCore
Definition qmutex.h:346
static int addAllMenuItemsToMenu(JNIEnv *env, jobject menu, QAndroidPlatformMenu *platformMenu)
static jmethodID setHeaderTitleContextMenuMethodID
void hideContextMenu(QAndroidPlatformMenu *menu)
static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
static QString removeAmpersandEscapes(QString s)
static QList< QAndroidPlatformMenu * > pendingContextMenus
static QSet< QAndroidPlatformMenuBar * > menuBars
void setActiveTopLevelWindow(QWindow *window)
void removeMenuBar(QAndroidPlatformMenuBar *menuBar)
void syncMenu(QAndroidPlatformMenu *)
static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu)
static jmethodID clearMenuMethodID
static QAndroidPlatformMenuBar * visibleMenuBar
static jmethodID addMenuItemMethodID
void addMenuBar(QAndroidPlatformMenuBar *menuBar)
static int menuNoneValue
static jmethodID setIconMenuItemMethodID
static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
static QWindow * activeTopLevelWindow
void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect)
static jmethodID setCheckableMenuItemMethodID
static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject thiz, jobject menu)
static QAndroidPlatformMenu * visibleMenu
static void fillMenuItem(JNIEnv *env, jobject menuItem, bool checkable, bool checked, bool enabled, bool visible, const QIcon &icon=QIcon())
bool registerNatives(QJniEnvironment &env)
static jmethodID setCheckedMenuItemMethodID
void setMenuBar(QAndroidPlatformMenuBar *menuBar, QWindow *window)
static jmethodID setVisibleMenuItemMethodID
static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
static jmethodID setEnabledMenuItemMethodID
static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
static JNINativeMethod methods[]
AndroidBackendRegister * backendRegister()
Q_DECLARE_JNI_CLASS(MotionEvent, "android/view/MotionEvent")