Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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{
25 static QList<QAndroidPlatformMenu *> pendingContextMenus;
28
29 static QSet<QAndroidPlatformMenuBar *> menuBars;
31 static QWindow *activeTopLevelWindow = nullptr;
32 Q_CONSTINIT static QRecursiveMutex menuBarMutex;
33
34 static jmethodID clearMenuMethodID = 0;
35 static jmethodID addMenuItemMethodID = 0;
36 static int menuNoneValue = 0;
38
39 static jmethodID setCheckableMenuItemMethodID = 0;
40 static jmethodID setCheckedMenuItemMethodID = 0;
41 static jmethodID setEnabledMenuItemMethodID = 0;
42 static jmethodID setIconMenuItemMethodID = 0;
43 static jmethodID setVisibleMenuItemMethodID = 0;
44
46 {
47 qtActivityDelegate().callMethod<void>("resetOptionsMenu");
48 }
49
51 {
52 qtActivityDelegate().callMethod<void>("openOptionsMenu");
53 }
54
56 {
58 if (visibleMenu)
62 qtActivityDelegate().callMethod<void>("openContextMenu",
63 anchorRect.x(), anchorRect.y(),
64 anchorRect.width(), anchorRect.height());
65 }
66
68 {
70 if (visibleMenu == menu) {
71 qtActivityDelegate().callMethod<void>("closeContextMenu");
72 pendingContextMenus.clear();
73 } else {
74 pendingContextMenus.removeOne(menu);
75 }
76 }
77
78 // FIXME
80 {
81// QMutexLocker lock(&visibleMenuMutex);
82// if (visibleMenu == menu)
83// {
84// hideContextMenu(menu);
85// showContextMenu(menu);
86// }
87 }
88
95
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
135
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 Q_UNUSED(thiz)
217
218 env->CallVoidMethod(menu, clearMenuMethodID);
220 if (!visibleMenuBar)
221 return JNI_FALSE;
222
224 int order = 0;
225 QMutexLocker lockMenuBarMutex(visibleMenuBar->menusListMutex());
226 if (menus.size() == 1) { // Expand the menu
227 order = addAllMenuItemsToMenu(env, menu, static_cast<QAndroidPlatformMenu *>(menus.front()));
228 } else {
229 for (QAndroidPlatformMenu *item : menus) {
230 QString itemText = removeAmpersandEscapes(item->text());
231 jstring jtext = env->NewString(reinterpret_cast<const jchar *>(itemText.data()),
232 itemText.length());
233 jint menuId = visibleMenuBar->menuId(item);
234 jobject menuItem = env->CallObjectMethod(menu,
237 menuId,
238 order++,
239 jtext);
240 env->DeleteLocalRef(jtext);
241
242 fillMenuItem(env,
243 menuItem,
244 false,
245 false,
246 item->isEnabled(),
247 item->isVisible(),
248 item->icon());
249 }
250 }
251 return order ? JNI_TRUE : JNI_FALSE;
252 }
253
254 static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
255 {
256 Q_UNUSED(env)
257 Q_UNUSED(thiz)
258
260 if (!visibleMenuBar)
261 return JNI_FALSE;
262
264 if (menus.size() == 1) { // Expanded menu
266 if (item) {
267 if (item->menu()) {
268 showContextMenu(item->menu(), QRect());
269 } else {
270 if (item->isCheckable())
271 item->setChecked(checked);
272 item->activated();
273 }
274 }
275 } else {
276 QAndroidPlatformMenu *menu = static_cast<QAndroidPlatformMenu *>(visibleMenuBar->menuForId(menuId));
277 if (menu)
279 }
280
281 return JNI_TRUE;
282 }
283
284 static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
285 {
286 Q_UNUSED(env)
287 Q_UNUSED(thiz)
289 }
290
291 static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
292 {
293 Q_UNUSED(thiz)
294
295 env->CallVoidMethod(menu, clearMenuMethodID);
297 if (!visibleMenu)
298 return;
299
300 QString menuText = removeAmpersandEscapes(visibleMenu->text());
301 jstring jtext = env->NewString(reinterpret_cast<const jchar*>(menuText.data()),
302 menuText.length());
303 env->CallObjectMethod(menu, setHeaderTitleContextMenuMethodID, jtext);
304 env->DeleteLocalRef(jtext);
306 }
307
308 static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
309 {
310 Q_UNUSED(thiz)
311 env->CallVoidMethod(menu, clearMenuMethodID);
313 if (!visibleMenu)
314 return;
315
317 }
318
319 static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
320 {
321 Q_UNUSED(env)
322 Q_UNUSED(thiz)
323
325 QAndroidPlatformMenuItem * item = static_cast<QAndroidPlatformMenuItem *>(visibleMenu->menuItemForId(menuId));
326 if (item) {
327 if (item->menu()) {
328 showContextMenu(item->menu(), QRect());
329 } else {
330 if (item->isCheckable())
331 item->setChecked(checked);
332 item->activated();
333 visibleMenu->aboutToHide();
334 visibleMenu = 0;
335 for (QAndroidPlatformMenu *menu : std::as_const(pendingContextMenus)) {
336 if (menu->isVisible())
337 menu->aboutToHide();
338 }
339 pendingContextMenus.clear();
340 }
341 }
342 return JNI_TRUE;
343 }
344
345 static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
346 {
347 Q_UNUSED(env)
348 Q_UNUSED(thiz)
350
352 if (!visibleMenu)
353 return;
354
355 visibleMenu->aboutToHide();
356 visibleMenu = 0;
357 if (!pendingContextMenus.empty())
359 }
360
361 static JNINativeMethod methods[] = {
362 {"onPrepareOptionsMenu", "(Landroid/view/Menu;)Z", (void *)onPrepareOptionsMenu},
363 {"onOptionsItemSelected", "(IZ)Z", (void *)onOptionsItemSelected},
364 {"onOptionsMenuClosed", "(Landroid/view/Menu;)V", (void*)onOptionsMenuClosed},
365 {"onCreateContextMenu", "(Landroid/view/ContextMenu;)V", (void *)onCreateContextMenu},
366 {"fillContextMenu", "(Landroid/view/Menu;)V", (void *)fillContextMenu},
367 {"onContextItemSelected", "(IZ)Z", (void *)onContextItemSelected},
368 {"onContextMenuClosed", "(Landroid/view/Menu;)V", (void*)onContextMenuClosed},
369 };
370
371#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
372 clazz = env->FindClass(CLASS_NAME); \
373 if (!clazz) { \
374 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), classErrorMsgFmt(), CLASS_NAME); \
375 return false; \
376 }
377
378#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
379 VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
380 if (!VAR) { \
381 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), METHOD_NAME, METHOD_SIGNATURE); \
382 return false; \
383 }
384
385#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
386 VAR = env->GetStaticMethodID(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_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
393 VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
394 if (!VAR) { \
395 __android_log_print(ANDROID_LOG_FATAL, qtTagText(), methodErrorMsgFmt(), FIELD_NAME, FIELD_SIGNATURE); \
396 return false; \
397 }
398
400 {
401 jclass appClass = applicationClass();
402
403 if (!env.registerNativeMethods(appClass, methods, sizeof(methods) / sizeof(methods[0]))) {
404 __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
405 return false;
406 }
407
408 jclass clazz;
409 FIND_AND_CHECK_CLASS("android/view/Menu");
410 GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V");
411 GET_AND_CHECK_METHOD(addMenuItemMethodID, clazz, "add", "(IIILjava/lang/CharSequence;)Landroid/view/MenuItem;");
412 jfieldID menuNoneFiledId;
413 GET_AND_CHECK_STATIC_FIELD(menuNoneFiledId, clazz, "NONE", "I");
414 menuNoneValue = env->GetStaticIntField(clazz, menuNoneFiledId);
415
416 FIND_AND_CHECK_CLASS("android/view/ContextMenu");
417 GET_AND_CHECK_METHOD(setHeaderTitleContextMenuMethodID, clazz, "setHeaderTitle","(Ljava/lang/CharSequence;)Landroid/view/ContextMenu;");
418
419 FIND_AND_CHECK_CLASS("android/view/MenuItem");
420 GET_AND_CHECK_METHOD(setCheckableMenuItemMethodID, clazz, "setCheckable", "(Z)Landroid/view/MenuItem;");
421 GET_AND_CHECK_METHOD(setCheckedMenuItemMethodID, clazz, "setChecked", "(Z)Landroid/view/MenuItem;");
422 GET_AND_CHECK_METHOD(setEnabledMenuItemMethodID, clazz, "setEnabled", "(Z)Landroid/view/MenuItem;");
423 GET_AND_CHECK_METHOD(setIconMenuItemMethodID, clazz, "setIcon", "(Landroid/graphics/drawable/Drawable;)Landroid/view/MenuItem;");
424 GET_AND_CHECK_METHOD(setVisibleMenuItemMethodID, clazz, "setVisible", "(Z)Landroid/view/MenuItem;");
425 return true;
426 }
427}
428
#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)
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:1019
@ 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:834
\inmodule QtGui
Definition qimage.h:37
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
reference front()
Definition qlist.h:687
void aboutToShow()
This signal is emitted just before the menu is shown to the user.
void aboutToHide()
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:239
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:185
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:236
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:188
\inmodule QtCore
Definition qmutex.h:309
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
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 jmethodID setVisibleMenuItemMethodID
static Q_CONSTINIT QRecursiveMutex menuBarMutex
static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
static QAndroidPlatformMenu * visibleMenu
static QString removeAmpersandEscapes(QString s)
static jmethodID setHeaderTitleContextMenuMethodID
static Q_CONSTINIT QRecursiveMutex visibleMenuMutex
void setActiveTopLevelWindow(QWindow *window)
void removeMenuBar(QAndroidPlatformMenuBar *menuBar)
static QWindow * activeTopLevelWindow
void syncMenu(QAndroidPlatformMenu *)
static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu)
static jmethodID setEnabledMenuItemMethodID
static jmethodID clearMenuMethodID
static JNINativeMethod methods[]
void addMenuBar(QAndroidPlatformMenuBar *menuBar)
static QList< QAndroidPlatformMenu * > pendingContextMenus
static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
static jmethodID setCheckedMenuItemMethodID
static QSet< QAndroidPlatformMenuBar * > menuBars
void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect)
static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject thiz, jobject menu)
static int menuNoneValue
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
static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
jobject createBitmap(QImage img, JNIEnv *env)
QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
jclass applicationClass()
jobject createBitmapDrawable(jobject bitmap, JNIEnv *env)
@ Desktop
Definition qnamespace.h:215
@ Popup
Definition qnamespace.h:211
@ Window
Definition qnamespace.h:207
@ Dialog
Definition qnamespace.h:208
@ Sheet
Definition qnamespace.h:209
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
GLdouble s
[6]
Definition qopenglext.h:235
GLint void * img
Definition qopenglext.h:233
GLfixed GLfixed GLint GLint order
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
QReadWriteLock lock
[0]
QGraphicsItem * item
QList< QTreeWidgetItem * > items
aWidget window() -> setWindowTitle("New Window Title")
[2]
QMenu menu
[5]
QMenuBar * menuBar
[0]