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 <assam.boudjelthia@qt.io>
(cherry picked from commit 9e167af3f6a4b2ac192aedd83f5066808d719415)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Tinja Paavoseppä 2024-07-25 15:38:37 +03:00 committed by Qt Cherry-pick Bot
parent 8d2f2cc791
commit e04eca32a0
7 changed files with 49 additions and 83 deletions

View File

@ -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

View File

@ -140,7 +140,6 @@ class QtActivityDelegate extends QtActivityDelegateBase
r.width(), kbHeight);
return true;
});
m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_layout));
}
@Override

View File

@ -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)

View File

@ -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(),

View File

@ -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);

View File

@ -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();
}

View File

@ -91,8 +91,7 @@ namespace QtAndroidInput
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>(
"updateHandles", QtAndroidPrivate::activity(),
qtLayout().object<QtJniTypes::QtLayout>(), mode, editMenuPos.x(), editMenuPos.y(),
"updateHandles", mode, editMenuPos.x(), editMenuPos.y(),
editButtons, cursor.x(), cursor.y(), anchor.x(), anchor.y(), rtl);
}