1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtVirtualKeyboard/private/shadowinputcontext_p.h> |
5 | #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> |
6 | #include <QtVirtualKeyboard/private/virtualkeyboarddebug_p.h> |
7 | |
8 | #include <QtCore/private/qobject_p.h> |
9 | #include <QGuiApplication> |
10 | #include <QQuickItem> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | namespace QtVirtualKeyboard { |
15 | |
16 | class ShadowInputContextPrivate : public QObjectPrivate |
17 | { |
18 | public: |
19 | ShadowInputContextPrivate() : |
20 | QObjectPrivate(), |
21 | inputContext(nullptr), |
22 | anchorRectIntersectsClipRect(false), |
23 | cursorRectIntersectsClipRect(false), |
24 | selectionControlVisible(false) |
25 | { |
26 | } |
27 | |
28 | QVirtualKeyboardInputContext *inputContext; |
29 | QPointer<QObject> inputItem; |
30 | QString preeditText; |
31 | QList<QInputMethodEvent::Attribute> preeditTextAttributes; |
32 | QRectF anchorRectangle; |
33 | QRectF cursorRectangle; |
34 | bool anchorRectIntersectsClipRect; |
35 | bool cursorRectIntersectsClipRect; |
36 | bool selectionControlVisible; |
37 | }; |
38 | |
39 | ShadowInputContext::ShadowInputContext(QObject *parent) : |
40 | QObject(*new ShadowInputContextPrivate(), parent) |
41 | { |
42 | } |
43 | |
44 | void ShadowInputContext::setInputContext(QVirtualKeyboardInputContext *inputContext) |
45 | { |
46 | Q_D(ShadowInputContext); |
47 | d->inputContext = inputContext; |
48 | } |
49 | |
50 | QObject *ShadowInputContext::inputItem() const |
51 | { |
52 | Q_D(const ShadowInputContext); |
53 | return d->inputItem.data(); |
54 | } |
55 | |
56 | void ShadowInputContext::setInputItem(QObject *inputItem) |
57 | { |
58 | Q_D(ShadowInputContext); |
59 | if (d->inputItem != inputItem) { |
60 | d->inputItem = inputItem; |
61 | emit inputItemChanged(); |
62 | update(queries: Qt::ImQueryAll); |
63 | } |
64 | } |
65 | |
66 | QRectF ShadowInputContext::anchorRectangle() const |
67 | { |
68 | Q_D(const ShadowInputContext); |
69 | return d->anchorRectangle; |
70 | } |
71 | |
72 | QRectF ShadowInputContext::cursorRectangle() const |
73 | { |
74 | Q_D(const ShadowInputContext); |
75 | return d->cursorRectangle; |
76 | } |
77 | |
78 | bool ShadowInputContext::anchorRectIntersectsClipRect() const |
79 | { |
80 | Q_D(const ShadowInputContext); |
81 | return d->anchorRectIntersectsClipRect; |
82 | } |
83 | |
84 | bool ShadowInputContext::cursorRectIntersectsClipRect() const |
85 | { |
86 | Q_D(const ShadowInputContext); |
87 | return d->cursorRectIntersectsClipRect; |
88 | } |
89 | |
90 | bool ShadowInputContext::selectionControlVisible() const |
91 | { |
92 | Q_D(const ShadowInputContext); |
93 | return d->selectionControlVisible; |
94 | } |
95 | |
96 | void ShadowInputContext::setSelectionOnFocusObject(const QPointF &anchorPos, const QPointF &cursorPos) |
97 | { |
98 | Q_D(ShadowInputContext); |
99 | QObject *focus = d->inputItem; |
100 | if (!focus) |
101 | return; |
102 | |
103 | QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: d->inputItem); |
104 | bool success; |
105 | int anchor = queryFocusObject(query: Qt::ImCursorPosition, argument: quickItem ? quickItem->mapFromScene(point: anchorPos) : anchorPos).toInt(ok: &success); |
106 | if (success) { |
107 | int cursor = queryFocusObject(query: Qt::ImCursorPosition, argument: quickItem ? quickItem->mapFromScene(point: cursorPos) : cursorPos).toInt(ok: &success); |
108 | if (success) { |
109 | if (anchor == cursor && anchorPos != cursorPos) |
110 | return; |
111 | QList<QInputMethodEvent::Attribute> imAttributes; |
112 | imAttributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant())); |
113 | QInputMethodEvent event(QString(), imAttributes); |
114 | QGuiApplication::sendEvent(receiver: QGuiApplication::focusObject(), event: &event); |
115 | } |
116 | } |
117 | } |
118 | |
119 | void ShadowInputContext::updateSelectionProperties() |
120 | { |
121 | Q_D(ShadowInputContext); |
122 | if (!d->inputItem) |
123 | return; |
124 | |
125 | QInputMethodQueryEvent imQueryEvent(Qt::ImAnchorRectangle | |
126 | Qt::ImCursorRectangle | |
127 | Qt::ImInputItemClipRectangle); |
128 | QGuiApplication::sendEvent(receiver: d->inputItem, event: &imQueryEvent); |
129 | QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: d->inputItem); |
130 | const QRectF anchorRect = imQueryEvent.value(query: Qt::ImAnchorRectangle).toRectF(); |
131 | const QRectF cursorRect = imQueryEvent.value(query: Qt::ImCursorRectangle).toRectF(); |
132 | const QRectF anchorRectangle = quickItem ? quickItem->mapRectToScene(rect: anchorRect) : anchorRect; |
133 | const QRectF cursorRectangle = quickItem ? quickItem->mapRectToScene(rect: cursorRect) : cursorRect; |
134 | const QRectF inputItemClipRect = imQueryEvent.value(query: Qt::ImInputItemClipRectangle).toRectF(); |
135 | const bool anchorRectIntersectsClipRect = inputItemClipRect.intersects(r: anchorRect); |
136 | const bool cursorRectIntersectsClipRect = inputItemClipRect.intersects(r: cursorRect); |
137 | const bool selectionControlVisible = d->inputContext->isSelectionControlVisible(); |
138 | |
139 | const bool newAnchorRectangle = anchorRectangle != d->anchorRectangle; |
140 | const bool newCursorRectangle = cursorRectangle != d->cursorRectangle; |
141 | const bool newAnchorRectIntersectsClipRect = anchorRectIntersectsClipRect != d->anchorRectIntersectsClipRect; |
142 | const bool newCursorRectIntersectsClipRect = cursorRectIntersectsClipRect != d->cursorRectIntersectsClipRect; |
143 | const bool newSelectionControlVisible = selectionControlVisible != d->selectionControlVisible; |
144 | |
145 | d->anchorRectangle = anchorRectangle; |
146 | d->cursorRectangle = cursorRectangle; |
147 | d->anchorRectIntersectsClipRect = anchorRectIntersectsClipRect; |
148 | d->cursorRectIntersectsClipRect = cursorRectIntersectsClipRect; |
149 | d->selectionControlVisible = selectionControlVisible; |
150 | |
151 | if (newAnchorRectangle) |
152 | emit anchorRectangleChanged(); |
153 | if (newCursorRectangle) |
154 | emit cursorRectangleChanged(); |
155 | if (newAnchorRectIntersectsClipRect) |
156 | emit anchorRectIntersectsClipRectChanged(); |
157 | if (newCursorRectIntersectsClipRect) |
158 | emit cursorRectIntersectsClipRectChanged(); |
159 | if (newSelectionControlVisible) |
160 | emit selectionControlVisibleChanged(); |
161 | } |
162 | |
163 | void ShadowInputContext::update(Qt::InputMethodQueries queries) |
164 | { |
165 | Q_UNUSED(queries); |
166 | Q_D(ShadowInputContext); |
167 | if (!d->inputItem) |
168 | return; |
169 | |
170 | QInputMethodQueryEvent imQueryEvent(Qt::ImQueryInput); |
171 | QGuiApplication::sendEvent(receiver: d->inputItem, event: &imQueryEvent); |
172 | |
173 | const QString surroundingText = imQueryEvent.value(query: Qt::ImSurroundingText).toString(); |
174 | const int cursorPosition = imQueryEvent.value(query: Qt::ImCursorPosition).toInt(); |
175 | const int anchorPosition = imQueryEvent.value(query: Qt::ImAnchorPosition).toInt(); |
176 | |
177 | const QString newSurroundingText = d->inputContext->surroundingText(); |
178 | const int newCursorPosition = d->inputContext->cursorPosition(); |
179 | const int newAnchorPosition = d->inputContext->anchorPosition(); |
180 | |
181 | const QString newPreeditText = d->inputContext->preeditText(); |
182 | const QList<QInputMethodEvent::Attribute> newPreeditAttributes = d->inputContext->preeditTextAttributes(); |
183 | |
184 | bool updateSurroundingText = newSurroundingText != surroundingText; |
185 | bool updateSelection = newCursorPosition != cursorPosition || newAnchorPosition != anchorPosition; |
186 | if (updateSurroundingText) { |
187 | QInputMethodEvent inputEvent; |
188 | inputEvent.setCommitString(commitString: newSurroundingText, replaceFrom: -cursorPosition, replaceLength: surroundingText.size()); |
189 | QGuiApplication::sendEvent(receiver: d->inputItem, event: &inputEvent); |
190 | } |
191 | |
192 | if (updateSurroundingText || updateSelection) { |
193 | QList<QInputMethodEvent::Attribute> attributes; |
194 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Selection, |
195 | newAnchorPosition, |
196 | newCursorPosition - newAnchorPosition, QVariant())); |
197 | QInputMethodEvent inputEvent(QString(), attributes); |
198 | QGuiApplication::sendEvent(receiver: d->inputItem, event: &inputEvent); |
199 | } |
200 | |
201 | const bool forcePreeditText = !newPreeditText.isEmpty() && (updateSurroundingText || updateSelection); |
202 | if (forcePreeditText || d->preeditText != newPreeditText || d->preeditTextAttributes != newPreeditAttributes) { |
203 | d->preeditText = newPreeditText; |
204 | d->preeditTextAttributes = newPreeditAttributes; |
205 | QInputMethodEvent inputEvent(d->preeditText, d->preeditTextAttributes); |
206 | QGuiApplication::sendEvent(receiver: d->inputItem, event: &inputEvent); |
207 | } |
208 | |
209 | updateSelectionProperties(); |
210 | } |
211 | |
212 | QVariant ShadowInputContext::queryFocusObject(Qt::InputMethodQuery query, QVariant argument) |
213 | { |
214 | Q_D(ShadowInputContext); |
215 | QVariant retval; |
216 | QObject *focusObject = d->inputItem; |
217 | if (!focusObject) |
218 | return retval; |
219 | |
220 | bool newMethodWorks = QMetaObject::invokeMethod(obj: focusObject, member: "inputMethodQuery" , |
221 | c: Qt::DirectConnection, |
222 | Q_RETURN_ARG(QVariant, retval), |
223 | Q_ARG(Qt::InputMethodQuery, query), |
224 | Q_ARG(QVariant, argument)); |
225 | if (newMethodWorks) |
226 | return retval; |
227 | |
228 | QInputMethodQueryEvent queryEvent(query); |
229 | QCoreApplication::sendEvent(receiver: focusObject, event: &queryEvent); |
230 | return queryEvent.value(query); |
231 | } |
232 | |
233 | } // namespace QtVirtualKeyboard |
234 | QT_END_NAMESPACE |
235 | |