Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandinputmethodeventbuilder.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QBrush>
8#include <QGuiApplication>
9#include <QInputMethod>
10#include <QPalette>
11#include <QTextCharFormat>
12
13#ifdef QT_BUILD_WAYLANDCOMPOSITOR_LIB
14#include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v2.h>
15#include <QtWaylandCompositor/private/qwayland-server-text-input-unstable-v3.h>
16#else
17#include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
18#include <QtWaylandClient/private/qwayland-text-input-unstable-v3.h>
19#endif
20
22
23QWaylandInputMethodEventBuilder::~QWaylandInputMethodEventBuilder()
24{
25}
26
27void QWaylandInputMethodEventBuilder::reset()
28{
29 m_anchor = 0;
30 m_cursor = 0;
31 m_deleteBefore = 0;
32 m_deleteAfter = 0;
33 m_preeditCursor = 0;
34 m_preeditStyles.clear();
35}
36
37void QWaylandInputMethodEventBuilder::setCursorPosition(int32_t index, int32_t anchor)
38{
39 m_cursor = index;
40 m_anchor = anchor;
41}
42
43void QWaylandInputMethodEventBuilder::setDeleteSurroundingText(uint32_t beforeLength, uint32_t afterLength)
44{
45 m_deleteBefore = beforeLength;
46 m_deleteAfter = afterLength;
47}
48
49void QWaylandInputMethodEventBuilder::addPreeditStyling(uint32_t index, uint32_t length, uint32_t style)
50{
51 QTextCharFormat format;
52
53 switch (style) {
54 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_NONE:
55 break;
56 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_DEFAULT:
57 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_UNDERLINE:
58 format.setFontUnderline(true);
59 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
60 m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format));
61 break;
62 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_ACTIVE:
63 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INACTIVE:
64 format.setFontWeight(QFont::Bold);
65 format.setFontUnderline(true);
66 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
67 m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format));
68 break;
69 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_HIGHLIGHT:
70 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_SELECTION:
71 {
72 format.setFontUnderline(true);
73 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
74 QPalette palette = qApp->palette();
75 format.setBackground(QBrush(palette.color(QPalette::Active, QPalette::Highlight)));
76 format.setForeground(QBrush(palette.color(QPalette::Active, QPalette::HighlightedText)));
77 m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format));
78 }
79 break;
80 case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INCORRECT:
81 format.setFontUnderline(true);
82 format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
83 format.setUnderlineColor(QColor(Qt::red));
84 m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format));
85 break;
86 default:
87 break;
88 }
89}
90
91void QWaylandInputMethodEventBuilder::setPreeditCursor(int32_t index)
92{
93 m_preeditCursor = index;
94}
95
96QInputMethodEvent *QWaylandInputMethodEventBuilder::buildCommit(const QString &text)
97{
98 QList<QInputMethodEvent::Attribute> attributes;
99
100 const std::pair<int, int> replacement = replacementForDeleteSurrounding();
101
102 if (m_cursor != 0 || m_anchor != 0) {
103 QString surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString();
104 const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
105 const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt();
106 const int absoluteCursor = QInputMethod::queryFocusObject(Qt::ImAbsolutePosition, QVariant()).toInt();
107
108 const int absoluteOffset = absoluteCursor - cursor;
109
110 const int cursorAfterCommit = qMin(anchor, cursor) + replacement.first + text.size();
111 surrounding.replace(qMin(anchor, cursor) + replacement.first,
112 qAbs(anchor - cursor) + replacement.second, text);
113
114 attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
115 indexFromWayland(surrounding, m_cursor, cursorAfterCommit) + absoluteOffset,
116 indexFromWayland(surrounding, m_anchor, cursorAfterCommit) + absoluteOffset,
117 QVariant()));
118 }
119
120 QInputMethodEvent *event = new QInputMethodEvent(QString(), attributes);
121 event->setCommitString(text, replacement.first, replacement.second);
122
123 return event;
124}
125
126QInputMethodEvent *QWaylandInputMethodEventBuilder::buildPreedit(const QString &text)
127{
128 QList<QInputMethodEvent::Attribute> attributes;
129
130 if (m_preeditCursor < 0) {
131 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
132 } else {
133 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, indexFromWayland(text, m_preeditCursor), 1, QVariant()));
134 }
135
136 for (const QInputMethodEvent::Attribute &attr : std::as_const(m_preeditStyles)) {
137 int start = indexFromWayland(text, attr.start);
138 int length = indexFromWayland(text, attr.start + attr.length) - start;
139 attributes.append(QInputMethodEvent::Attribute(attr.type, start, length, attr.value));
140 }
141
142 QInputMethodEvent *event = new QInputMethodEvent(text, attributes);
143
144 const std::pair<int, int> replacement = replacementForDeleteSurrounding();
145 event->setCommitString(QString(), replacement.first, replacement.second);
146
147 return event;
148}
149
150std::pair<int, int> QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding()
151{
152 if (m_deleteBefore == 0 && m_deleteAfter == 0)
153 return {0, 0};
154
155 const QString &surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString();
156 const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt();
157 const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt();
158
159 const int selectionStart = qMin(cursor, anchor);
160 const int selectionEnd = qMax(cursor, anchor);
161
162 const int deleteBefore = selectionStart - indexFromWayland(surrounding, -m_deleteBefore, selectionStart);
163 const int deleteAfter = indexFromWayland(surrounding, m_deleteAfter, selectionEnd) - selectionEnd;
164
165 return {-deleteBefore, deleteBefore + deleteAfter};
166}
167
169{
170 uint32_t hint = ZWP_TEXT_INPUT_V2_CONTENT_HINT_NONE;
171 uint32_t purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL;
172
173 if (hints & Qt::ImhHiddenText)
174 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT;
175 if (hints & Qt::ImhSensitiveData)
176 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA;
177 if ((hints & Qt::ImhNoAutoUppercase) == 0)
178 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION;
179 if (hints & Qt::ImhPreferNumbers) {
180 // Nothing yet
181 }
182 if (hints & Qt::ImhPreferUppercase)
183 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE;
184 if (hints & Qt::ImhPreferLowercase)
185 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE;
186 if ((hints & Qt::ImhNoPredictiveText) == 0) {
187 hint |= (ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION
188 | ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION);
189 }
190
191 if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0)
192 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE;
193 else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime))
194 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATETIME;
195 else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime))
196 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME;
197
198 if (hints & Qt::ImhPreferLatin)
199 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN;
200 if (hints & Qt::ImhMultiLine)
201 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE;
202 if (hints & Qt::ImhDigitsOnly)
203 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS;
204 if (hints & Qt::ImhFormattedNumbersOnly)
205 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER;
206 if (hints & Qt::ImhUppercaseOnly)
207 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE;
208 if (hints & Qt::ImhLowercaseOnly)
209 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE;
210 if (hints & Qt::ImhDialableCharactersOnly)
211 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE;
212 if (hints & Qt::ImhEmailCharactersOnly)
213 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL;
214 if (hints & Qt::ImhUrlCharactersOnly)
215 purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL;
216 if (hints & Qt::ImhLatinOnly)
217 hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN;
218
219 return QWaylandInputMethodContentType{hint, purpose};
220}
221
223{
224 uint32_t hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
225 uint32_t purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
226
227 if (hints & Qt::ImhHiddenText)
228 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT;
229 if (hints & Qt::ImhSensitiveData)
230 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
231 if ((hints & Qt::ImhNoAutoUppercase) == 0)
232 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
233 if (hints & Qt::ImhPreferNumbers) {
234 // Nothing yet
235 }
236 if (hints & Qt::ImhPreferUppercase)
237 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
238 if (hints & Qt::ImhPreferLowercase)
239 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE;
240 if ((hints & Qt::ImhNoPredictiveText) == 0) {
241 hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION
242 | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
243 }
244
245 if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0)
246 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE;
247 else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime))
248 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME;
249 else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime))
250 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME;
251 if (hints & Qt::ImhPreferLatin)
252 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN;
253 if (hints & Qt::ImhMultiLine)
254 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
255 if (hints & Qt::ImhDigitsOnly)
256 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS;
257 if (hints & Qt::ImhFormattedNumbersOnly)
258 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
259 if (hints & Qt::ImhUppercaseOnly)
260 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
261 if (hints & Qt::ImhLowercaseOnly)
262 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE;
263 if (hints & Qt::ImhDialableCharactersOnly)
264 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE;
265 if (hints & Qt::ImhEmailCharactersOnly)
266 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
267 if (hints & Qt::ImhUrlCharactersOnly)
268 purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL;
269 if (hints & Qt::ImhLatinOnly)
270 hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN;
271
272 return QWaylandInputMethodContentType{hint, purpose};
273}
274
275int QWaylandInputMethodEventBuilder::indexFromWayland(const QString &text, int length, int base)
276{
277 if (length == 0)
278 return base;
279
280 if (length < 0) {
281 const QByteArray &utf8 = QStringView{text}.left(base).toUtf8();
282 return QString::fromUtf8(utf8.first(qMax(utf8.size() + length, 0))).size();
283 } else {
284 const QByteArray &utf8 = QStringView{text}.mid(base).toUtf8();
285 return QString::fromUtf8(utf8.first(qMin(length, utf8.size()))).size() + base;
286 }
287}
288
289int QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(const QString &text, int length, int base)
290{
291 if (length == 0)
292 return base;
293
294 if (length < 0) {
295 const QByteArray &utf8 = QStringView{text}.left(base).toUtf8();
296 const int len = utf8.size();
297 const int start = len + length;
298 if (start <= 0)
299 return 0;
300
301 for (int i = 0; i < 4; i++) {
302 if (start + i >= len)
303 return base;
304
305 const unsigned char ch = utf8.at(start + i);
306 // check if current character is a utf8's initial character.
307 if (ch < 0x80 || ch > 0xbf)
308 return QString::fromUtf8(utf8.left(start + i)).size();
309 }
310 } else {
311 const QByteArray &utf8 = QStringView{text}.mid(base).toUtf8();
312 const int len = utf8.size();
313 const int start = length;
314 if (start >= len)
315 return base + QString::fromUtf8(utf8).size();
316
317 for (int i = 0; i < 4; i++) {
318 const unsigned char ch = utf8.at(start - i);
319 // check if current character is a utf8's initial character.
320 if (ch < 0x80 || ch > 0xbf)
321 return base + QString::fromUtf8(utf8.left(start - i)).size();
322 }
323 }
324 return -1;
325}
326
327int QWaylandInputMethodEventBuilder::indexToWayland(const QString &text, int length, int base)
328{
329 return QStringView{text}.mid(base, length).toUtf8().size();
330}
331
332QT_END_NAMESPACE
Combined button and popup list for selecting options.