From 8379186009bc9d0a9ef6309c7508fd529b7eec12 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Tue, 9 Jan 2018 12:54:22 +0200 Subject: Android: rewrite edit context menu - get rid of the old tool bar with edit controls which was also dropped by Google after they realized that is not intuitive at all. - we now introduce a nice context menu as we see in modern Android devices. This menu works on all Android devices starting with API 16. [ChangeLog][Android] Say hello to Android edit context menu Change-Id: I00d0d83fe8876335c72d7b183db4c1b53746d6b7 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/android/jar/jar.pro | 2 +- .../org/qtproject/qt5/android/CursorHandle.java | 9 +- .../org/qtproject/qt5/android/EditContextView.java | 120 +++++++++++++++++ .../src/org/qtproject/qt5/android/EditMenu.java | 142 --------------------- .../org/qtproject/qt5/android/EditPopupMenu.java | 85 ++++++------ .../qtproject/qt5/android/QtActivityDelegate.java | 48 +++---- .../src/org/qtproject/qt5/android/QtNative.java | 5 +- src/plugins/platforms/android/androidjniinput.cpp | 9 +- src/plugins/platforms/android/androidjniinput.h | 2 +- .../platforms/android/qandroidinputcontext.cpp | 25 ++-- .../platforms/android/qandroidinputcontext.h | 8 ++ 11 files changed, 230 insertions(+), 225 deletions(-) create mode 100644 src/android/jar/src/org/qtproject/qt5/android/EditContextView.java delete mode 100644 src/android/jar/src/org/qtproject/qt5/android/EditMenu.java diff --git a/src/android/jar/jar.pro b/src/android/jar/jar.pro index 683866a345..bda15a0a00 100644 --- a/src/android/jar/jar.pro +++ b/src/android/jar/jar.pro @@ -19,7 +19,7 @@ JAVASOURCES += \ $$PATHPREFIX/QtNativeLibrariesDir.java \ $$PATHPREFIX/QtSurface.java \ $$PATHPREFIX/ExtractStyle.java \ - $$PATHPREFIX/EditMenu.java \ + $$PATHPREFIX/EditContextView.java \ $$PATHPREFIX/EditPopupMenu.java \ $$PATHPREFIX/CursorHandle.java \ $$PATHPREFIX/QtThread.java diff --git a/src/android/jar/src/org/qtproject/qt5/android/CursorHandle.java b/src/android/jar/src/org/qtproject/qt5/android/CursorHandle.java index 4f2c06644d..788a5c2b3d 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/CursorHandle.java +++ b/src/android/jar/src/org/qtproject/qt5/android/CursorHandle.java @@ -142,7 +142,6 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener m_cursorView = new CursorView(context, this); m_cursorView.setImageDrawable(drawable); - // m_layout.addView(m_cursorView); m_popup = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle); m_popup.setSplitTouchEnabled(true); @@ -185,6 +184,14 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener m_posY = y; } + public int bottom() + { + initOverlay(); + final int[] location = new int[2]; + m_cursorView.getLocationOnScreen(location); + return location[1] + m_cursorView.getHeight(); + } + public void hide() { if (m_popup != null) { m_popup.dismiss(); diff --git a/src/android/jar/src/org/qtproject/qt5/android/EditContextView.java b/src/android/jar/src/org/qtproject/qt5/android/EditContextView.java new file mode 100644 index 0000000000..6d9987ca2a --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/EditContextView.java @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2018 BogDan Vatra +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Android port of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + + +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.R; + +import java.util.HashMap; + +public class EditContextView extends LinearLayout implements View.OnClickListener +{ + public static final int CUT_BUTTON = 1 << 0; + public static final int COPY_BUTTON = 1 << 1; + public static final int PASTE_BUTTON = 1 << 2; + public static final int SALL_BUTTON = 1 << 3; + + HashMap m_buttons = new HashMap(4); + OnClickListener m_onClickListener; + + public interface OnClickListener + { + void contextButtonClicked(int buttonId); + } + + private class ContextButton extends TextView + { + public int m_buttonId; + public ContextButton(Context context, int stringId) { + super(context); + m_buttonId = stringId; + setText(stringId); + setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + setGravity(Gravity.CENTER); + setTextColor(getResources().getColor(R.color.widget_edittext_dark)); + EditContextView.this.setBackground(getResources().getDrawable(R.drawable.editbox_background_normal)); + float scale = getResources().getDisplayMetrics().density; + int hPadding = (int)(16 * scale + 0.5f); + int vPadding = (int)(8 * scale + 0.5f); + setPadding(hPadding, vPadding, hPadding, vPadding); + setOnClickListener(EditContextView.this); + } + } + + @Override + public void onClick(View v) + { + ContextButton button = (ContextButton)v; + m_onClickListener.contextButtonClicked(button.m_buttonId); + } + + void addButton(int id) + { + ContextButton button = new ContextButton(getContext(), id); + m_buttons.put(id, button); + addView(button); + } + + public void updateButtons(int buttonsLayout) + { + m_buttons.get(R.string.cut).setVisibility((buttonsLayout & CUT_BUTTON) != 0 ? View.VISIBLE : View.GONE); + m_buttons.get(R.string.copy).setVisibility((buttonsLayout & COPY_BUTTON) != 0 ? View.VISIBLE : View.GONE); + m_buttons.get(R.string.paste).setVisibility((buttonsLayout & PASTE_BUTTON) != 0 ? View.VISIBLE : View.GONE); + m_buttons.get(R.string.selectAll).setVisibility((buttonsLayout & SALL_BUTTON) != 0 ? View.VISIBLE : View.GONE); + } + + public EditContextView(Context context, OnClickListener onClickListener) { + super(context); + m_onClickListener = onClickListener; + setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + addButton(R.string.cut); + addButton(R.string.copy); + addButton(R.string.paste); + addButton(R.string.selectAll); + } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/EditMenu.java b/src/android/jar/src/org/qtproject/qt5/android/EditMenu.java deleted file mode 100644 index afe7797914..0000000000 --- a/src/android/jar/src/org/qtproject/qt5/android/EditMenu.java +++ /dev/null @@ -1,142 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Olivier Goffart -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the Android port of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -package org.qtproject.qt5.android; - -import android.view.ActionMode; -import android.view.ActionMode.Callback; -import android.view.Menu; -import android.view.MenuItem; -import android.app.Activity; -import android.content.Context; -import android.content.res.TypedArray; - -/** - * The edit menu actions (when there is selection) - */ -class EditMenu implements ActionMode.Callback { - - private final Activity m_activity; - private ActionMode m_actionMode; - - public EditMenu(Activity activity) { - m_activity = activity; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mode.setTitle(null); - mode.setSubtitle(null); - mode.setTitleOptionalHint(true); - - Context context = m_activity; - int[] attrs = { - android.R.attr.actionModeSelectAllDrawable, android.R.attr.actionModeCutDrawable, - android.R.attr.actionModeCopyDrawable, android.R.attr.actionModePasteDrawable - }; - TypedArray a = context.getTheme().obtainStyledAttributes(attrs); - - menu.add(Menu.NONE, android.R.id.selectAll, Menu.NONE, android.R.string.selectAll) - .setIcon(a.getResourceId(0, 0)) - .setAlphabeticShortcut('a') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(Menu.NONE, android.R.id.cut, Menu.NONE, android.R.string.cut) - .setIcon(a.getResourceId(1, 0)) - .setAlphabeticShortcut('x') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(Menu.NONE, android.R.id.copy, Menu.NONE, android.R.string.copy) - .setIcon(a.getResourceId(2, 0)) - .setAlphabeticShortcut('c') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - menu.add(Menu.NONE, android.R.id.paste, Menu.NONE, android.R.string.paste) - .setIcon(a.getResourceId(3, 0)) - .setAlphabeticShortcut('v') - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - m_actionMode = null; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - - switch (item.getItemId()) { - case android.R.id.cut: - return QtNativeInputConnection.cut(); - case android.R.id.copy: - return QtNativeInputConnection.copy(); - case android.R.id.paste: - return QtNativeInputConnection.paste(); - case android.R.id.selectAll: - return QtNativeInputConnection.selectAll(); - } - return false; - } - - public void hide() - { - if (m_actionMode != null) { - m_actionMode.finish(); - } - } - - public void show() - { - if (m_actionMode == null) { - m_actionMode = m_activity.startActionMode(this); - } - } - - public boolean isShown() - { - return m_actionMode != null; - } -} diff --git a/src/android/jar/src/org/qtproject/qt5/android/EditPopupMenu.java b/src/android/jar/src/org/qtproject/qt5/android/EditPopupMenu.java index 246be1aeb2..d065cd8549 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/EditPopupMenu.java +++ b/src/android/jar/src/org/qtproject/qt5/android/EditPopupMenu.java @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2018 BogDan Vatra ** Copyright (C) 2016 Olivier Goffart ** Contact: http://www.qt.io/licensing/ ** @@ -55,59 +56,51 @@ import android.view.ViewTreeObserver; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup; +import android.R; // Helper class that manages a cursor or selection handle -public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, OnClickListener +public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, EditContextView.OnClickListener { private View m_layout = null; - private View m_view = null; + private EditContextView m_view = null; private PopupWindow m_popup = null; - private Activity m_activity; private int m_posX; private int m_posY; + private int m_buttons; - public EditPopupMenu(Activity activity, View layout) { - m_activity = activity; + public EditPopupMenu(Activity activity, View layout) + { + m_view = new EditContextView(activity, this); m_layout = layout; } - private boolean initOverlay(){ - if (m_popup == null){ - Context context = m_layout.getContext(); - int[] attrs = { android.R.attr.textEditPasteWindowLayout }; - TypedArray a = context.getTheme().obtainStyledAttributes(attrs); - final int layout = a.getResourceId(0, 0); - LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - m_view = inflater.inflate(layout, null); - - final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - m_view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - m_view.measure(size, size); - m_view.setOnClickListener(this); - - m_popup = new PopupWindow(context, 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); - } - return true; + private void initOverlay() + { + if (m_popup != null) + return; + + Context context = m_layout.getContext(); + m_popup = new PopupWindow(context, 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); } public int getHeight() { - initOverlay(); return m_view.getHeight(); } // Show the handle at a given position (or move it if it is already shown) - public void setPosition(final int x, final int y){ + public void setPosition(final int x, final int y, final int buttons) + { initOverlay(); + m_view.updateButtons(buttons); final int[] location = new int[2]; m_layout.getLocationOnScreen(location); @@ -115,9 +108,12 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, OnClic int y2 = y + location[1]; x2 -= m_view.getWidth() / 2 ; + + if (m_layout.getWidth() < x + m_view.getWidth() / 2) + x2 = m_layout.getWidth() - m_view.getWidth(); + if (x2 < 0) x2 = 0; - y2 -= m_view.getHeight(); if (m_popup.isShowing()) m_popup.update(x2, y2, -1, -1); @@ -126,12 +122,13 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, OnClic m_posX = x; m_posY = y; - + m_buttons = buttons; } public void hide() { if (m_popup != null) { m_popup.dismiss(); + m_popup = null; } } @@ -141,15 +138,27 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, OnClic // 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); + setPosition(m_posX, m_posY, m_buttons); return true; } @Override - public void onClick(View v) { - QtNativeInputConnection.paste(); + public void contextButtonClicked(int buttonId) { + switch (buttonId) { + case R.string.cut: + QtNativeInputConnection.cut(); + break; + case R.string.copy: + QtNativeInputConnection.copy(); + break; + case R.string.paste: + QtNativeInputConnection.paste(); + break; + case R.string.selectAll: + QtNativeInputConnection.selectAll(); + break; + } hide(); } } - diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java index 517b79f01e..aeb5515a01 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -151,7 +151,6 @@ public class QtActivityDelegate private CursorHandle m_cursorHandle; private CursorHandle m_leftSelectionHandle; private CursorHandle m_rightSelectionHandle; - private EditMenu m_editMenu; private EditPopupMenu m_editPopupMenu; public void setFullScreen(boolean enterFullScreen) @@ -488,13 +487,12 @@ public class QtActivityDelegate private static final int CursorHandleShowNormal = 1; private static final int CursorHandleShowSelection = 2; private static final int CursorHandleShowEdit = 0x100; - private static final int CursorHandleShowPopup = 0x200; /* called from the C++ code when the position of the cursor or selection handles needs to be adjusted. mode is one of QAndroidInputContext::CursorHandleShowMode */ - public void updateHandles(int mode, int x1, int y1, int x2, int y2, boolean rtl) + public void updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl) { switch (mode & 0xff) { @@ -509,21 +507,10 @@ public class QtActivityDelegate m_rightSelectionHandle = null; m_leftSelectionHandle = null; } - if (m_editMenu != null) { - m_editMenu.hide(); - m_editMenu = null; - } - if (m_editPopupMenu != null) { - m_editPopupMenu.hide(); - m_editPopupMenu = null; - } + m_editPopupMenu.hide(); break; case CursorHandleShowNormal: - if (m_editMenu != null) { - m_editMenu.hide(); - m_editMenu = null; - } if (m_cursorHandle == null) { m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle, android.R.attr.textSelectHandle, false); @@ -554,22 +541,28 @@ public class QtActivityDelegate m_cursorHandle.hide(); m_cursorHandle = null; } - if (m_editMenu == null) - m_editMenu = new EditMenu(m_activity); - m_editMenu.show(); + mode |= CursorHandleShowEdit; break; } - if ((mode & CursorHandleShowPopup) == CursorHandleShowPopup && QtNative.hasClipboardText()) { - if (m_editPopupMenu == null) - m_editPopupMenu = new EditPopupMenu(m_activity, m_layout); - if (y2 < m_editPopupMenu.getHeight()) { - // If the popup cannot be shown over the text, it must be shown under the anchors - y2 = y1 + 2 * m_editPopupMenu.getHeight(); + + if (QtNative.hasClipboardText()) + editButtons |= EditContextView.PASTE_BUTTON; + else + editButtons &= ~EditContextView.PASTE_BUTTON; + + if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) { + editY -= m_editPopupMenu.getHeight(); + if (editY < 0) { + if (m_cursorHandle != null) + editY = m_cursorHandle.bottom(); + else if (m_leftSelectionHandle != null && m_rightSelectionHandle != null) + editY = Math.max(m_leftSelectionHandle.bottom(), m_rightSelectionHandle.bottom()); + else + return; } - m_editPopupMenu.setPosition(x2, y2); - } else if (m_editPopupMenu != null) { + m_editPopupMenu.setPosition(editX, editY, editButtons); + } else { m_editPopupMenu.hide(); - m_editPopupMenu = null; } } @@ -1024,6 +1017,7 @@ public class QtActivityDelegate return true; } }); + m_editPopupMenu = new EditPopupMenu(m_activity, m_layout); } public void hideSplashScreen() diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java index 3db3453263..6b54ae45e7 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java @@ -585,6 +585,9 @@ public class QtNative } private static void updateHandles(final int mode, + final int editX, + final int editY, + final int editButtons, final int x1, final int y1, final int x2, @@ -594,7 +597,7 @@ public class QtNative runAction(new Runnable() { @Override public void run() { - m_activityDelegate.updateHandles(mode, x1, y1, x2, y2, rtl); + m_activityDelegate.updateHandles(mode, editX, editY, editButtons, x1, y1, x2, y2, rtl); } }); } diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index c4142a9b6e..a714a56338 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -124,11 +124,12 @@ namespace QtAndroidInput return m_softwareKeyboardRect; } - void updateHandles(int mode, QPoint cursor, QPoint anchor, bool rtl) + void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { - QJNIObjectPrivate::callStaticMethod(applicationClass(), "updateHandles", "(IIIIIZ)V", - mode, cursor.x(), cursor.y(), anchor.x(), - anchor.y(), rtl); + QJNIObjectPrivate::callStaticMethod(applicationClass(), "updateHandles", "(IIIIIIIIZ)V", + mode, editMenuPos.x(), editMenuPos.y(), editButtons, + cursor.x(), cursor.y(), + anchor.x(), anchor.y(), rtl); } static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y) diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index c09b426f49..cc3070c4aa 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -58,7 +58,7 @@ namespace QtAndroidInput // Software keyboard support // cursor/selection handles - void updateHandles(int handleCount, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); + void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); bool registerNatives(JNIEnv *env); } diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 430eaf638b..d90e0ac1ba 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -581,8 +581,9 @@ void QAndroidInputContext::updateSelectionHandles() ? QHighDpiScaling::factor(window) : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen()); - QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled | Qt::ImCurrentSelection); + QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled | Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText); QCoreApplication::sendEvent(m_focusObject, &query); + int cpos = query.value(Qt::ImCursorPosition).toInt(); int anchor = query.value(Qt::ImAnchorPosition).toInt(); @@ -594,17 +595,20 @@ void QAndroidInputContext::updateSelectionHandles() auto curRect = im->cursorRectangle(); QPoint cursorPoint(curRect.center().x(), curRect.bottom()); - QPoint editMenuPoint(curRect.center().x(), curRect.top()); - QtAndroidInput::updateHandles(m_handleMode, cursorPoint * pixelDensity, - editMenuPoint * pixelDensity); + QPoint editMenuPoint(curRect.x(), curRect.y()); + m_handleMode &= ShowEditPopup; m_handleMode |= ShowCursor; + uint32_t buttons = EditContext::PasteButton; + if (!query.value(Qt::ImSurroundingText).toString().isEmpty()) + buttons |= EditContext::SelectAllButton; + QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, cursorPoint * pixelDensity); // The VK is hidden, reset the timer if (m_hideCursorHandleTimer.isActive()) m_hideCursorHandleTimer.start(); return; } - m_handleMode |= ShowSelection; + m_handleMode = ShowSelection | ShowEditPopup ; auto leftRect = im->cursorRectangle(); auto rightRect = im->anchorRectangle(); if (cpos > anchor) @@ -612,7 +616,8 @@ void QAndroidInputContext::updateSelectionHandles() QPoint leftPoint(leftRect.bottomLeft().toPoint() * pixelDensity); QPoint righPoint(rightRect.bottomRight().toPoint() * pixelDensity); - QtAndroidInput::updateHandles(ShowSelection, leftPoint, righPoint, + QPoint editPoint(leftRect.united(rightRect).topLeft().toPoint() * pixelDensity); + QtAndroidInput::updateHandles(m_handleMode, editPoint, EditContext::AllButtons, leftPoint, righPoint, query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); m_hideCursorHandleTimer.stop(); } @@ -1230,21 +1235,21 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) jboolean QAndroidInputContext::selectAll() { - m_handleMode &= ~ShowEditPopup; + m_handleMode = ShowCursor; sendShortcut(QKeySequence::SelectAll); return JNI_TRUE; } jboolean QAndroidInputContext::cut() { - m_handleMode &= ~ShowEditPopup; + m_handleMode = ShowCursor; sendShortcut(QKeySequence::Cut); return JNI_TRUE; } jboolean QAndroidInputContext::copy() { - m_handleMode &= ~ShowEditPopup; + m_handleMode = ShowCursor; sendShortcut(QKeySequence::Copy); return JNI_TRUE; } @@ -1258,7 +1263,7 @@ jboolean QAndroidInputContext::copyURL() jboolean QAndroidInputContext::paste() { finishComposingText(); - m_handleMode &= ~ShowEditPopup; + m_handleMode = ShowCursor; sendShortcut(QKeySequence::Paste); return JNI_TRUE; } diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h index 1b351f16bd..71c6a2b1e9 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.h +++ b/src/plugins/platforms/android/qandroidinputcontext.h @@ -59,6 +59,14 @@ class QAndroidInputContext: public QPlatformInputContext }; public: + enum EditContext : uint32_t { + CutButton = 1 << 0, + CopyButton = 1 << 1, + PasteButton = 1 << 2, + SelectAllButton = 1 << 3, + AllButtons = CutButton | CopyButton | PasteButton | SelectAllButton + }; + enum HandleMode { Hidden = 0, ShowCursor = 1, -- cgit v1.2.3