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
12QT_BEGIN_NAMESPACE
13
14namespace QtVirtualKeyboard {
15
16class ShadowInputContextPrivate : public QObjectPrivate
17{
18public:
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
39ShadowInputContext::ShadowInputContext(QObject *parent) :
40 QObject(*new ShadowInputContextPrivate(), parent)
41{
42}
43
44void ShadowInputContext::setInputContext(QVirtualKeyboardInputContext *inputContext)
45{
46 Q_D(ShadowInputContext);
47 d->inputContext = inputContext;
48}
49
50QObject *ShadowInputContext::inputItem() const
51{
52 Q_D(const ShadowInputContext);
53 return d->inputItem.data();
54}
55
56void 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
66QRectF ShadowInputContext::anchorRectangle() const
67{
68 Q_D(const ShadowInputContext);
69 return d->anchorRectangle;
70}
71
72QRectF ShadowInputContext::cursorRectangle() const
73{
74 Q_D(const ShadowInputContext);
75 return d->cursorRectangle;
76}
77
78bool ShadowInputContext::anchorRectIntersectsClipRect() const
79{
80 Q_D(const ShadowInputContext);
81 return d->anchorRectIntersectsClipRect;
82}
83
84bool ShadowInputContext::cursorRectIntersectsClipRect() const
85{
86 Q_D(const ShadowInputContext);
87 return d->cursorRectIntersectsClipRect;
88}
89
90bool ShadowInputContext::selectionControlVisible() const
91{
92 Q_D(const ShadowInputContext);
93 return d->selectionControlVisible;
94}
95
96void 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
119void 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
163void 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
212QVariant 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
234QT_END_NAMESPACE
235

source code of qtvirtualkeyboard/src/virtualkeyboard/shadowinputcontext.cpp