From e04eca32a005028e89eac9ebd3c2707ec5e4a0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinja=20Paavosepp=C3=A4?= Date: Thu, 25 Jul 2024 15:38:37 +0300 Subject: [PATCH] Android: Move EditPopupMenu to QtWindow The EditPopupMenu requires a View in its constructor, making it important that we do not hold a reference to it after that View is no longer valid. In cases where we do not have one, top level View that will stay valid for the lifetime of the whole Activity, e.g. when embedding a QtView to non-Qt Android Activities, making the popups QtWindow based makes sense, as the lifecycle of the popup and it's corresponding QtWindow should match. Task-number: QTBUG-126180 Change-Id: Ibb45513de98f79a293a05eeb317d959ac0328dbe Reviewed-by: Assam Boudjelthia (cherry picked from commit 9e167af3f6a4b2ac192aedd83f5066808d719415) Reviewed-by: Qt Cherry-pick Bot --- .../qtproject/qt/android/EditPopupMenu.java | 50 ++++++++++--------- .../qt/android/QtActivityDelegate.java | 1 - .../org/qtproject/qt/android/QtEditText.java | 14 ++++++ .../qt/android/QtEmbeddedDelegate.java | 13 ----- .../qtproject/qt/android/QtInputDelegate.java | 47 ++--------------- .../qt/android/QtInputInterface.java | 4 +- .../platforms/android/androidjniinput.cpp | 3 +- 7 files changed, 49 insertions(+), 83 deletions(-) diff --git a/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java b/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java index e9838bc5bee..ca79bfd3804 100644 --- a/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java +++ b/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java @@ -7,6 +7,7 @@ package org.qtproject.qt.android; import android.app.Activity; import android.content.Context; import android.graphics.Point; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -16,22 +17,23 @@ import android.widget.PopupWindow; class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayoutChangeListener, EditContextView.OnClickListener { - private final View m_layout; private final EditContextView m_view; + private final QtEditText m_editText; + private PopupWindow m_popup = null; private final Activity m_activity; private int m_posX; private int m_posY; private int m_buttons; - private QtEditText m_currentEditText = null; // TODO, get rid of this reference - EditPopupMenu(Activity activity, View layout) + EditPopupMenu(QtEditText editText) { - m_activity = activity; - m_view = new EditContextView(activity, this); + m_activity = (Activity) editText.getContext(); + m_view = new EditContextView(m_activity, this); m_view.addOnLayoutChangeListener(this); - m_layout = layout; + m_editText = editText; + m_editText.getViewTreeObserver().addOnPreDrawListener(this); } private void initOverlay() @@ -39,19 +41,16 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout if (m_popup != null) return; - Context context = m_layout.getContext(); - m_popup = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle); + m_popup = new PopupWindow(m_activity, null, android.R.attr.textSelectHandleWindowStyle); m_popup.setSplitTouchEnabled(true); m_popup.setClippingEnabled(false); m_popup.setContentView(m_view); m_popup.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); m_popup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); - - m_layout.getViewTreeObserver().addOnPreDrawListener(this); } // Show the handle at a given position (or move it if it is already shown) - void setPosition(final int x, final int y, final int buttons, final QtEditText editText) + void setPosition(final int x, final int y, final int buttons) { initOverlay(); @@ -59,7 +58,7 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout Point viewSize = m_view.getCalculatedSize(); final int[] layoutLocation = new int[2]; - m_layout.getLocationOnScreen(layoutLocation); + m_editText.getLocationOnScreen(layoutLocation); // These values are used for handling split screen case final int[] activityLocation = new int[2]; @@ -73,15 +72,21 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout x2 -= viewSize.x / 2 ; y2 -= viewSize.y; - if (y2 < 0 && editText != null) { - y2 = editText.getSelectionHandleBottom(); + if (y2 < 0) + y2 = m_editText.getSelectionHandleBottom(); + + if (y2 <= 0) { + try { + QtLayout parentLayout = (QtLayout) m_editText.getParent(); + parentLayout.requestLayout(); + } catch (ClassCastException e) { + Log.w(QtNative.QtTAG, "QtEditText " + m_editText + " parent is not a QtLayout, " + + "requestLayout() skipped"); + } } - if (y2 <= 0) - m_layout.requestLayout(); - - if (m_layout.getWidth() < x + viewSize.x / 2) - x2 = m_layout.getWidth() - viewSize.x; + if (m_editText.getWidth() < x + viewSize.x / 2) + x2 = m_editText.getWidth() - viewSize.x; if (x2 < 0) x2 = 0; @@ -89,12 +94,11 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout if (m_popup.isShowing()) m_popup.update(x2, y2, -1, -1); else - m_popup.showAtLocation(m_layout, 0, x2, y2); + m_popup.showAtLocation(m_editText, 0, x2, y2); m_posX = x; m_posY = y; m_buttons = buttons; - m_currentEditText = editText; } void hide() { @@ -110,7 +114,7 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout // For example if the keyboard appears. // Adjust the position of the handle accordingly if (m_popup != null && m_popup.isShowing()) - setPosition(m_posX, m_posY, m_buttons, m_currentEditText); + setPosition(m_posX, m_posY, m_buttons); return true; } @@ -121,7 +125,7 @@ class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayout { if ((right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop) && m_popup != null && m_popup.isShowing()) - setPosition(m_posX, m_posY, m_buttons, m_currentEditText); + setPosition(m_posX, m_posY, m_buttons); } @Override diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 9dacc4b3e70..7491dc5c90d 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -140,7 +140,6 @@ class QtActivityDelegate extends QtActivityDelegateBase r.width(), kbHeight); return true; }); - m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_layout)); } @Override diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java index 144ad2e8784..d00fc76d860 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java @@ -61,12 +61,15 @@ class QtEditText extends View private CursorHandle m_leftSelectionHandle; private CursorHandle m_rightSelectionHandle; + final private EditPopupMenu m_editPopupMenu; + QtEditText(Context context, QtInputConnectionListener listener) { super(context); setFocusable(true); setFocusableInTouchMode(true); m_qtInputConnectionListener = listener; + m_editPopupMenu = new EditPopupMenu(this); } private void setImeOptions(int imeOptions) @@ -299,8 +302,19 @@ class QtEditText extends View m_cursorHandle.hide(); m_cursorHandle = null; } + mode |= CursorHandleShowEdit; break; } + + if (!QtClipboardManager.hasClipboardText(getContext())) + editButtons &= ~EditContextView.PASTE_BUTTON; + + final boolean setEditPopupPosition = (mode & QtEditText.CursorHandleShowEdit) == + QtEditText.CursorHandleShowEdit && editButtons != 0; + if (setEditPopupPosition) + m_editPopupMenu.setPosition(editX, editY, editButtons); + else + m_editPopupMenu.hide(); } private boolean isDisablePredictiveTextWorkaround(int inputHints) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java index fb996e1c654..3da520c31b3 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java @@ -105,7 +105,6 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase BackendRegister.unregisterBackend(QtMenuInterface.class); BackendRegister.unregisterBackend(QtInputInterface.class); } - updateInputDelegate(); } } @@ -151,21 +150,9 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase public void setView(QtView view) { m_view = view; - updateInputDelegate(); } // QtEmbeddedViewInterface implementation end - private void updateInputDelegate() { - // If the QtView has attached to the window before Qt libs have been loaded, - // the input delegate will be null - if (m_inputDelegate == null) - return; - if (m_view == null) - m_inputDelegate.setEditPopupMenu(null); - else - m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_view)); - } - private void createRootWindow() { if (m_view != null && !m_windowLoaded) { QtView.createRootWindow(m_view, m_view.getLeft(), m_view.getTop(), m_view.getWidth(), diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index 980bc7da47b..3075d49ee03 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -52,8 +52,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt private int m_landscapeKeyboardHeight = 0; private int m_probeKeyboardHeightDelayMs = 50; - private EditPopupMenu m_editPopupMenu; - private int m_softInputMode = 0; private static Boolean m_tabletEventSupported = null; @@ -149,12 +147,13 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt mode is one of QAndroidInputContext::CursorHandleShowMode */ @Override - public void updateHandles(Activity activity, QtLayout layout, int mode, - int editX, int editY, int editButtons, + public void updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl) { - QtNative.runAction(() -> updateHandleImpl(activity, layout, mode, editX, editY, editButtons, - x1, y1, x2, y2, rtl)); + QtNative.runAction(() -> { + if (m_currentEditText != null) + m_currentEditText.updateHandles(mode, editX, editY, editButtons, x1, y1, x2, y2, rtl); + }); } @Override @@ -247,11 +246,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt return m_currentEditText; } - void setEditPopupMenu(EditPopupMenu editPopupMenu) - { - m_editPopupMenu = editPopupMenu; - } - private void keyboardVisibilityUpdated(boolean visibility) { m_isKeyboardHidingAnimationOngoing = false; @@ -352,37 +346,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt }, m_probeKeyboardHeightDelayMs); } - private void updateHandleImpl(Activity activity, QtLayout layout, int mode, - int editX, int editY, int editButtons, - int x1, int y1, int x2, int y2, boolean rtl) - { - if (m_currentEditText != null) - m_currentEditText.updateHandles(mode, editX, editY, editButtons, x1, y1, x2, y2, rtl); - - switch (mode & 0xff) - { - case QtEditText.CursorHandleNotShown: - if (m_editPopupMenu != null) - m_editPopupMenu.hide(); - break; - case QtEditText.CursorHandleShowSelection: - mode |= QtEditText.CursorHandleShowEdit; - break; - } - - if (!QtClipboardManager.hasClipboardText(activity)) - editButtons &= ~EditContextView.PASTE_BUTTON; - - if (m_editPopupMenu != null) { - if ((mode & QtEditText.CursorHandleShowEdit) == QtEditText.CursorHandleShowEdit && - editButtons != 0) { - m_editPopupMenu.setPosition(editX, editY, editButtons, m_currentEditText); - } else { - m_editPopupMenu.hide(); - } - } - } - boolean onKeyDown(int keyCode, KeyEvent event) { m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java index af90e230ab1..afe57e51e77 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java @@ -15,7 +15,7 @@ interface QtInputInterface { void hideSoftwareKeyboard(); boolean isSoftwareKeyboardVisible(); int getSelectionHandleWidth(); - void updateHandles(Activity activity, QtLayout layout, int mode, int editX, int editY, - int editButtons, int x1, int y1, int x2, int y2, boolean rtl); + void updateHandles(int mode, int editX, int editY, int editButtons, + int x1, int y1, int x2, int y2, boolean rtl); QtInputConnection.QtInputConnectionListener getInputConnectionListener(); } diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 732791bc8a0..a0faedcc5b2 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -91,8 +91,7 @@ namespace QtAndroidInput { AndroidBackendRegister *reg = QtAndroid::backendRegister(); reg->callInterface( - "updateHandles", QtAndroidPrivate::activity(), - qtLayout().object(), mode, editMenuPos.x(), editMenuPos.y(), + "updateHandles", mode, editMenuPos.x(), editMenuPos.y(), editButtons, cursor.x(), cursor.y(), anchor.x(), anchor.y(), rtl); }