Skip to content

Commit e06077e

Browse files
committed
fix: forward InputMethodQuery to searchEdit for correct IME candidate window positioning
When launcher opens, focus is on InputEventItem (root node) while searchEdit has no focus or cursor, as per design requirements. However, IMEs like Sogou send InputMethodQuery events to the focused control during pre-edit stage to query cursor position for candidate window placement. Since InputEventItem is not a text input control, it cannot return meaningful cursor position, causing the candidate window to appear at incorrect positions (e.g., top-left corner or other wrong locations). This commit implements a forwarding mechanism: when InputEventItem receives InputMethodQuery, it forwards the query to searchEdit and maps the returned cursor rectangle coordinates from searchEdit's local coordinate system to the query target's coordinate system, then returns the mapped result to the IME framework. This allows the candidate window to be positioned correctly near the search box while keeping focus on InputEventItem (no visible cursor in search box). Changes: - Add inputMethodSource property to InputEventItem for specifying the text control to forward queries to - Implement InputMethodQuery event forwarding and coordinate mapping in eventFilter - Set inputMethodSource to searchEdit in FullscreenFrame and WindowedFrame - Clean up temporary diagnostic logs 启动器打开时,焦点在 InputEventItem(根节点)上,searchEdit 无焦点、无光标,这是设计要求。但搜狗等输入法在预编辑阶段会向焦点控件发送 InputMethodQuery 事件查询光标位置以定位候选框。由于 InputEventItem 不是文本输入控件,无法返回有意义的光标位置,导致候选框出现在错误位置(如屏幕左上角或其他位置)。 本次提交实现了转发机制:InputEventItem 收到 InputMethodQuery 时,将查询转发给 searchEdit,并将 searchEdit 返回的光标矩形坐标从其局部坐标系映射到查询目标的坐标系,再将映射后的结果返回给输入法框架。这样候选框能正确定位到搜索框附近,同时焦点保持在 InputEventItem 上(搜索框不显示光标)。 修改内容: - 为 InputEventItem 添加 inputMethodSource 属性,用于指定转发查询的文本控件 - 在 eventFilter 中实现 InputMethodQuery 事件转发和坐标映射 - 在 FullscreenFrame 和 WindowedFrame 中设置 inputMethodSource 为 searchEdit - 清理临时诊断日志 PMS: BUG-301743
1 parent a8dd424 commit e06077e

4 files changed

Lines changed: 75 additions & 17 deletions

File tree

inputeventitem.cpp

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44
#include "inputeventitem.h"
5+
#include <QKeyEvent>
6+
#include <QInputMethodQueryEvent>
7+
#include <QCoreApplication>
58
#include <QLoggingCategory>
69
namespace {
710
Q_LOGGING_CATEGORY(logInputEvent, "org.deepin.dde.launchpad.input")
@@ -12,14 +15,59 @@ InputEventItem::InputEventItem()
1215
qApp->installEventFilter(this);
1316
}
1417

18+
QQuickItem* InputEventItem::inputMethodSource() const
19+
{
20+
return m_inputMethodSource;
21+
}
22+
23+
void InputEventItem::setInputMethodSource(QQuickItem* source)
24+
{
25+
if (m_inputMethodSource != source) {
26+
m_inputMethodSource = source;
27+
Q_EMIT inputMethodSourceChanged();
28+
}
29+
}
30+
1531
bool InputEventItem::eventFilter(QObject *obj, QEvent *event) {
16-
if (event->type() == QEvent::InputMethod && (this->children().contains(obj) || obj == this)) {
32+
bool isTarget = this->children().contains(obj) || obj == this;
33+
if (event->type() == QEvent::InputMethod) {
1734
QInputMethodEvent *inputMethodEvent = static_cast<QInputMethodEvent *>(event);
18-
qCDebug(logInputEvent) << "Input method event received:" << inputMethodEvent->commitString();
19-
if (!inputMethodEvent->commitString().isEmpty()) {
20-
qCInfo(logInputEvent) << "Emitting input received signal:" << inputMethodEvent->commitString();
21-
Q_EMIT inputReceived(inputMethodEvent->commitString());
35+
qCDebug(logInputEvent) << "InputMethod event: obj=" << obj
36+
<< "isTarget=" << isTarget
37+
<< "preedit=" << inputMethodEvent->preeditString()
38+
<< "commit=" << inputMethodEvent->commitString();
39+
if (isTarget) {
40+
if (!inputMethodEvent->preeditString().isEmpty()) {
41+
Q_EMIT preeditReceived(inputMethodEvent->preeditString());
42+
}
43+
if (!inputMethodEvent->commitString().isEmpty()) {
44+
Q_EMIT inputReceived(inputMethodEvent->commitString());
45+
}
46+
}
47+
}
48+
if (event->type() == QEvent::InputMethodQuery && isTarget && m_inputMethodSource) {
49+
QInputMethodQueryEvent *queryEvent = static_cast<QInputMethodQueryEvent*>(event);
50+
// Forward the query to searchEdit to get cursor position for IME candidate window
51+
QInputMethodQueryEvent forwardEvent(queryEvent->queries());
52+
QCoreApplication::sendEvent(m_inputMethodSource, &forwardEvent);
53+
// Map ImCursorRectangle coordinates from searchEdit to this item's coordinate system
54+
if (queryEvent->queries() & Qt::ImCursorRectangle) {
55+
QRectF rect = forwardEvent.value(Qt::ImCursorRectangle).toRectF();
56+
QPointF mapped = m_inputMethodSource->mapToItem(qobject_cast<QQuickItem*>(obj), rect.topLeft());
57+
rect.moveTopLeft(mapped);
58+
queryEvent->setValue(Qt::ImCursorRectangle, rect);
2259
}
60+
// Tell IME this control accepts input
61+
if (queryEvent->queries() & Qt::ImEnabled) {
62+
queryEvent->setValue(Qt::ImEnabled, true);
63+
}
64+
return true;
65+
}
66+
if (event->type() == QEvent::KeyPress) {
67+
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
68+
qCDebug(logInputEvent) << "KeyPress event: obj=" << obj
69+
<< "key=" << keyEvent->key()
70+
<< "text=" << keyEvent->text();
2371
}
2472
return QObject::eventFilter(obj, event);
2573
}

inputeventitem.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44
#ifndef INPUTEVENTITEM_H
@@ -12,14 +12,23 @@ class InputEventItem : public QQuickItem
1212
{
1313
Q_OBJECT
1414
QML_ELEMENT
15+
Q_PROPERTY(QQuickItem* inputMethodSource READ inputMethodSource WRITE setInputMethodSource NOTIFY inputMethodSourceChanged)
1516
public:
1617
InputEventItem();
1718

19+
QQuickItem* inputMethodSource() const;
20+
void setInputMethodSource(QQuickItem* source);
21+
1822
protected:
1923
bool eventFilter(QObject *obj, QEvent *event) override;
2024

2125
signals:
2226
void inputReceived(const QString &input);
27+
void preeditReceived(const QString &preedit);
28+
void inputMethodSourceChanged();
29+
30+
private:
31+
QQuickItem* m_inputMethodSource = nullptr;
2332
};
2433

2534
#endif // INPUTEVENTITEM_H

qml/FullscreenFrame.qml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'windowed' as WindowedLaunchpad
1818
InputEventItem {
1919
anchors.fill: parent
2020
objectName: "FullscreenFrame-InputEventItem"
21+
inputMethodSource: searchEdit
2122

2223
property Palette appTextColor: Palette {
2324
normal {
@@ -883,12 +884,8 @@ InputEventItem {
883884
Connections {
884885
target: LauncherController
885886
function onVisibleChanged() {
886-
if (LauncherController.visible) {
887-
searchEdit.forceActiveFocus()
888-
return
889-
}
890-
891887
// only do these clean-up steps on launcher get hide
888+
if (LauncherController.visible) return
892889
// clear searchEdit text
893890
searchEdit.text = ""
894891
if (listviewPage.currentItem) {
@@ -912,4 +909,7 @@ InputEventItem {
912909
searchEdit.focus = true
913910
}
914911
}
912+
onPreeditReceived: function(text){
913+
searchEdit.forceActiveFocus()
914+
}
915915
}

qml/windowed/WindowedFrame.qml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "."
1616
InputEventItem {
1717
id: baseLayer
1818
objectName: "WindowedFrame-BaseLayer"
19+
inputMethodSource: bottomBar.searchEdit
1920

2021
visible: true
2122
focus: true
@@ -293,6 +294,9 @@ InputEventItem {
293294
bottomBar.searchEdit.focus = true
294295
}
295296
}
297+
onPreeditReceived: function(text){
298+
bottomBar.searchEdit.forceActiveFocus()
299+
}
296300

297301
Component.onCompleted: {
298302
// Since LauncherController onVisibleChanged only reset state on visible === false,
@@ -303,12 +307,9 @@ InputEventItem {
303307
Connections {
304308
target: LauncherController
305309
function onVisibleChanged() {
306-
if (LauncherController.visible) {
307-
bottomBar.searchEdit.forceActiveFocus()
308-
return
309-
}
310-
311310
// only do these clean-up steps on launcher get hide
311+
if (LauncherController.visible) return
312+
312313
// clear searchEdit text
313314
bottomBar.searchEdit.text = ""
314315
// reset(remove) keyboard focus

0 commit comments

Comments
 (0)