7@implementation QNSView (ComplexText)
11- (QObject*)focusObject
16 if (!m_platformWindow)
19 return m_platformWindow->window()->focusObject();
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42- (
void)insertText:(id)text replacementRange:(NSRange)replacementRange
44 qCDebug(lcQpaKeys).nospace() <<
"Inserting \"" << text <<
"\""
45 <<
", replacing range " << replacementRange;
47 NSString *string = [self stringForText:text];
49 if (m_composingText.isEmpty()) {
55 if ([string isEqualToString:m_currentlyInterpretedKeyEvent.characters]) {
58 qCDebug(lcQpaKeys) <<
"Ignoring text insertion for simple text";
59 m_sendKeyEvent =
true;
64 if (queryInputMethod(self.focusObject)) {
65 QInputMethodEvent inputMethodEvent;
67 QString commitString = QString::fromNSString(string);
70 replacementRange = [self sanitizeReplacementRange:replacementRange];
74 auto [replaceFrom, replaceLength] = [self inputMethodRangeForRange:replacementRange];
76 if (replaceFrom == NSNotFound) {
77 qCWarning(lcQpaKeys) <<
"Failed to compute valid replacement range for text insertion";
78 inputMethodEvent.setCommitString(commitString);
80 qCDebug(lcQpaKeys) <<
"Replacing from" << replaceFrom <<
"with length" << replaceLength
81 <<
"based on replacement range" << replacementRange;
82 inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength);
85 QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
88 m_composingText.clear();
89 m_composingFocusObject =
nullptr;
92- (
void)insertNewline:(id)sender
96 if (!m_platformWindow)
122 KeyEvent newlineEvent(m_currentlyInterpretedKeyEvent);
123 newlineEvent.type = QEvent::KeyPress;
125 const bool isEnter = newlineEvent.modifiers & Qt::KeypadModifier;
126 newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return;
127 newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode)
128 : QLatin1Char(kReturnCharCode);
129 newlineEvent.nativeVirtualKey = isEnter ? quint32(kVK_ANSI_KeypadEnter)
130 : quint32(kVK_Return);
132 qCDebug(lcQpaKeys) <<
"Inserting newline via" << newlineEvent;
133 newlineEvent.sendWindowSystemEvent(m_platformWindow->window());
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158- (
void)setMarkedText:(id)text selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
160 qCDebug(lcQpaKeys).nospace() <<
"Marking \"" << text <<
"\""
161 <<
" with selected range " << selectedRange
162 <<
", replacing range " << replacementRange;
164 const bool isAttributedString = [text isKindOfClass:NSAttributedString.
class];
165 QString preeditString = QString::fromNSString([self stringForText:text]);
167 QList<QInputMethodEvent::Attribute> preeditAttributes;
175 const bool showCursor = !selectedRange.length;
176 preeditAttributes << QInputMethodEvent::Attribute(
177 QInputMethodEvent::Cursor, selectedRange.location, showCursor);
185 QTextCharFormat selectionFormat;
186 auto *platformTheme = QGuiApplicationPrivate::platformTheme();
187 auto *systemPalette = platformTheme->palette();
188 selectionFormat.setBackground(systemPalette->color(QPalette::Highlight));
189 preeditAttributes << QInputMethodEvent::Attribute(
190 QInputMethodEvent::TextFormat,
191 selectedRange.location, selectedRange.length,
195 int composingLength = preeditString.length();
196 while (index < composingLength) {
197 NSRange range = NSMakeRange(index, composingLength - index);
199 static NSDictionary *defaultMarkedTextAttributes = []{
200 NSTextView *textView = [[NSTextView
new] autorelease];
201 return [textView.markedTextAttributes retain];
204 NSDictionary *attributes = isAttributedString
205 ? [text attributesAtIndex:index longestEffectiveRange:&range inRange:range]
206 : defaultMarkedTextAttributes;
208 qCDebug(lcQpaKeys) <<
"Decorating range" << range <<
"based on" << attributes;
209 QTextCharFormat format;
211 if (NSNumber *underlineStyle = attributes[NSUnderlineStyleAttributeName]) {
212 format.setFontUnderline(
true);
213 NSUnderlineStyle style = underlineStyle.integerValue;
214 if (style & NSUnderlineStylePatternDot)
215 format.setUnderlineStyle(QTextCharFormat::DotLine);
216 else if (style & NSUnderlineStylePatternDash)
217 format.setUnderlineStyle(QTextCharFormat::DashUnderline);
218 else if (style & NSUnderlineStylePatternDashDot)
219 format.setUnderlineStyle(QTextCharFormat::DashDotLine);
220 if (style & NSUnderlineStylePatternDashDotDot)
221 format.setUnderlineStyle(QTextCharFormat::DashDotDotLine);
223 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
229 if (NSColor *underlineColor = attributes[NSUnderlineColorAttributeName])
230 format.setUnderlineColor(qt_mac_toQColor(underlineColor));
231 if (NSColor *foregroundColor = attributes[NSForegroundColorAttributeName])
232 format.setForeground(qt_mac_toQColor(foregroundColor));
233 if (NSColor *backgroundColor = attributes[NSBackgroundColorAttributeName])
234 format.setBackground(qt_mac_toQColor(backgroundColor));
236 if (format != QTextCharFormat()) {
237 preeditAttributes << QInputMethodEvent::Attribute(
238 QInputMethodEvent::TextFormat, range.location, range.length, format);
241 index = range.location + range.length;
245 replacementRange = [self sanitizeReplacementRange:replacementRange];
249 auto [replaceFrom, replaceLength] = [self inputMethodRangeForRange:replacementRange];
252 m_composingText = preeditString;
254 if (QObject *focusObject = self.focusObject) {
255 m_composingFocusObject = focusObject;
256 if (queryInputMethod(focusObject)) {
257 QInputMethodEvent event(preeditString, preeditAttributes);
258 if (replaceLength > 0) {
262 qCDebug(lcQpaKeys) <<
"Replacing from" << replaceFrom <<
"with length"
263 << replaceLength <<
"based on replacement range" << replacementRange;
264 event.setCommitString(QString(), replaceFrom, replaceLength);
266 QCoreApplication::sendEvent(focusObject, &event);
271- (NSArray<NSString *> *)validAttributesForMarkedText
274 NSUnderlineColorAttributeName,
275 NSUnderlineStyleAttributeName,
276 NSForegroundColorAttributeName,
277 NSBackgroundColorAttributeName
283 return !m_composingText.isEmpty();
287
288
289
290
291
292
293
294- (NSRange)markedRange
296 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) {
297 int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
309 return NSMakeRange(absoluteCursorPosition, m_composingText.length());
311 return {NSNotFound, 0};
316
317
318
319
320
321
322
327 qCDebug(lcQpaKeys) <<
"Unmarking" << m_composingText
328 <<
"for focus object" << m_composingFocusObject;
330 if (!m_composingText.isEmpty()) {
331 QObject *focusObject = self.focusObject;
332 if (queryInputMethod(focusObject)) {
334 e.setCommitString(m_composingText);
335 QCoreApplication::sendEvent(focusObject, &e);
339 m_composingText.clear();
340 m_composingFocusObject =
nullptr;
344
345
346
347
348
349
350- (
void)cancelComposingText
352 if (m_composingText.isEmpty())
355 qCDebug(lcQpaKeys) <<
"Canceling composition" << m_composingText
356 <<
"for focus object" << m_composingFocusObject;
358 if (queryInputMethod(m_composingFocusObject)) {
360 QCoreApplication::sendEvent(m_composingFocusObject, &e);
363 m_composingText.clear();
364 m_composingFocusObject =
nullptr;
369- (
void)doCommandBySelector:(SEL)selector
377 qCDebug(lcQpaKeys) <<
"Trying to perform command" << selector;
378 if (![self tryToPerform:selector with:self]) {
379 m_sendKeyEvent =
true;
381 if (![NSStringFromSelector(selector) hasPrefix:@
"insert"]) {
389 m_sendKeyEventWithoutText =
true;
397
398
399
400
401
402- (NSRange)selectedRange
404 if (
auto queryResult = queryInputMethod(self.focusObject,
405 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) {
412 int cursorPosition = queryResult.value(Qt::ImCursorPosition).toInt();
413 int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
414 int absoluteOffset = absoluteCursorPosition - cursorPosition;
416 int anchorPosition = absoluteOffset + queryResult.value(Qt::ImAnchorPosition).toInt();
417 int selectionStart = anchorPosition >= absoluteCursorPosition ? absoluteCursorPosition : anchorPosition;
418 int selectionEnd = selectionStart == anchorPosition ? absoluteCursorPosition : anchorPosition;
419 int selectionLength = selectionEnd - selectionStart;
429 return NSMakeRange(selectionStart, selectionLength);
431 return {NSNotFound, 0};
436
437
438
439
440
441
442
443
444
445
446
447
448
449- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
451 if (
auto queryResult = queryInputMethod(self.focusObject,
452 Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) {
453 const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
454 const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString();
455 const QString textAfterCursor = queryResult.value(Qt::ImTextAfterCursor).toString();
460 const QString availableText = textBeforeCursor + m_composingText + textAfterCursor;
461 const NSRange availableRange = NSMakeRange(absoluteCursorPosition - textBeforeCursor.length(),
462 availableText.length());
464 const NSRange intersectedRange = NSIntersectionRange(range, availableRange);
466 *actualRange = intersectedRange;
468 if (!intersectedRange.length)
471 NSString *substring = QStringView(availableText).mid(
472 intersectedRange.location - availableRange.location,
473 intersectedRange.length).toNSString();
475 return [[[NSAttributedString alloc] initWithString:substring] autorelease];
483
484
485
486
487
488
489
490
491
492
493
494- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
497 Q_UNUSED(actualRange);
499 QWindow *window = m_platformWindow ? m_platformWindow->window() :
nullptr;
500 if (window && queryInputMethod(window->focusObject())) {
502 qCWarning(lcQpaKeys) <<
"Can't satisfy firstRectForCharacterRange for" << range;
503 QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect();
504 cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft()));
505 return QCocoaScreen::mapToNative(cursorRect);
511- (
NSUInteger)characterIndexForPoint:(NSPoint)point
519
520
521
522
523
531 auto level = m_platformWindow ? m_platformWindow->nativeWindow().level
532 : NSNormalWindowLevel;
541 return qMax(level, NSPopUpMenuWindowLevel);
547
548
549
550
551
552
553
554
555
556
557- (NSRange)sanitizeReplacementRange:(NSRange)range
559 if (range.location != NSNotFound)
565 const auto markedRange = [self markedRange];
566 const auto selectedRange = [self selectedRange];
568 if (markedRange.length)
570 else if (selectedRange.length)
571 return selectedRange;
578
579
580
581
582
583- (std::pair<
long long,
long long>)inputMethodRangeForRange:(NSRange)replacementRange
585 long long replaceFrom = replacementRange.location;
586 long long replaceLength = replacementRange.length;
588 const auto markedRange = [self markedRange];
589 const auto selectedRange = [self selectedRange];
591 if (markedRange.length && selectedRange.length) {
593 qCWarning(lcQpaKeys) <<
"Got both markedRange" << markedRange
594 <<
"and selectedRange" << selectedRange;
597 if (markedRange.length) {
601 replaceLength -= markedRange.length;
605 replaceFrom -= markedRange.location;
606 }
else if (selectedRange.length) {
607 if (!NSEqualRanges(NSIntersectionRange(replacementRange, selectedRange), selectedRange)) {
608 qCWarning(lcQpaKeys) <<
"Replacement range" << replacementRange
609 <<
"is a subset of selection" << selectedRange;
619 replaceLength -= selectedRange.length;
625 replaceFrom -= selectedRange.location;
626 }
else if (markedRange.location != NSNotFound) {
629 replaceFrom -= markedRange.location;
636 replaceLength = qMax(0ll, replaceLength);
638 return {replaceFrom, replaceLength};
643 return [text isKindOfClass:NSAttributedString.
class] ? [text string] : text;
648@implementation QNSView (ServicesMenu)
656- (id)validRequestorForSendType:(NSPasteboardType)sendType returnType:(NSPasteboardType)returnType
658 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImReadOnly | Qt::ImCurrentSelection)) {
659 bool canWriteToPasteboard =
false;
660 bool canReadFromPastboard =
false;
662 auto currentSelection = queryResult.value(Qt::ImCurrentSelection);
663 if (
auto *mimeData = currentSelection.value<QMimeData*>()) {
666 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
667 auto availableConverters = QMacMimeRegistry::all(scope);
668 auto sendUti = [self utiForPasteboardType:sendType];
669 auto returnUti = [self utiForPasteboardType:returnType];
670 const auto mimeFormats = mimeData->formats();
671 for (
const auto *c : availableConverters) {
672 if (mimeFormats.contains(c->mimeForUti(sendUti)))
673 canWriteToPasteboard =
true;
674 if (mimeFormats.contains(c->mimeForUti(returnUti)))
675 canReadFromPastboard =
true;
676 if (canWriteToPasteboard && canReadFromPastboard)
680 canWriteToPasteboard = [sendType isEqualToString:NSPasteboardTypeString]
681 && !currentSelection.toString().isEmpty();
682 canReadFromPastboard = [returnType isEqualToString:NSPasteboardTypeString]
683 && !queryResult.value(Qt::ImReadOnly).toBool();
686 if (!((sendType && !canWriteToPasteboard) || (returnType && !canReadFromPastboard))) {
687 qCDebug(lcQpaServices) <<
"Accepting service interaction for send" << sendType <<
"and receive" << returnType;
692 return [super validRequestorForSendType:sendType returnType:returnType];
695- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray<NSPasteboardType> *)types
697 bool didWrite =
false;
699 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImCurrentSelection)) {
700 auto currentSelection = queryResult.value(Qt::ImCurrentSelection);
701 if (
auto *mimeData = currentSelection.value<QMimeData*>()) {
702 auto mimeFormats = mimeData->formats();
703 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
704 auto availableConverters = QMacMimeRegistry::all(scope);
705 for (NSPasteboardType type in types) {
706 auto uti = [self utiForPasteboardType:type];
708 qCWarning(lcQpaServices) <<
"Did not find UTI for type" << type;
711 for (
const auto *converter : availableConverters) {
712 auto mime = converter->mimeForUti(uti);
713 if (mimeFormats.contains(mime)) {
714 auto utiDataList = converter->convertFromMime(mime,
715 mimeData->data(mime), uti);
716 if (utiDataList.isEmpty())
718 auto utiData = utiDataList.first();
719 qCDebug(lcQpaServices) <<
"Writing" << utiData <<
"to service pasteboard"
720 <<
"with UTI" << uti <<
"for type" << type <<
"based on mime" << mime;
721 didWrite |= [pasteboard setData:utiData.toNSData() forType:type];
729 if (!didWrite && ([types containsObject:NSPasteboardTypeString]
730 || QT_IGNORE_DEPRECATIONS([types containsObject:NSStringPboardType]))) {
731 auto selectedText = currentSelection.toString();
732 qCDebug(lcQpaServices) <<
"Writing" << selectedText <<
"to service pasteboard"
733 <<
"as pain text" <<
"for type" << NSPasteboardTypeString;
734 didWrite |= [pasteboard writeObjects:@[ selectedText.toNSString() ]];
741- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
743 if (queryInputMethod(self.focusObject)) {
744 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
745 QMacPasteboard macPasteboard(CFStringRef(pasteboard.name), scope);
746 auto *mimeData = macPasteboard.mimeData();
747 if (mimeData->formats().isEmpty()) {
748 qCWarning(lcQpaServices) <<
"Failed to resolve mime data from" << pasteboard.types;
752 qCDebug(lcQpaServices) <<
"Replacing selected range" << [self selectedRange]
753 <<
"with mime data" << [&]() {
754 QMap<QString, QByteArray> formatMap;
755 for (
const auto &format : mimeData->formats())
756 formatMap.insert(format, mimeData->data(format));
758 }() <<
"from service pasteboard" << pasteboard.name;
760 QList<QInputMethodEvent::Attribute> attributes;
761 attributes << QInputMethodEvent::Attribute(
762 QInputMethodEvent::MimeData,
763 0, 0, QVariant::fromValue(mimeData));
765 QInputMethodEvent inputMethodEvent(QString(), attributes);
769 inputMethodEvent.setCommitString(mimeData->text());
770 QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
777- (
QString)utiForPasteboardType:(NSPasteboardType)pasteboardType
782 UTType *uttype = [UTType typeWithIdentifier:pasteboardType];
788 uttype = [UTType typeWithTag:pasteboardType
789 tagClass:QT_IGNORE_DEPRECATIONS((NSString*)kUTTagClassNSPboardType)
790 conformingToType:nil];
792 return QString::fromNSString(uttype.identifier);
797#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000
)
798@implementation QNSView (ContentSelectionInfo)
801
802
803
804
805- (NSRect)selectionAnchorRect
807 if (queryInputMethod(self.focusObject)) {
810 const auto *inputMethod = qApp->inputMethod();
811 auto cursorRect = inputMethod->cursorRectangle();
812 auto anchorRect = inputMethod->anchorRectangle();
813 auto selectionRect = cursorRect.united(anchorRect);
814 if (cursorRect.top() != anchorRect.top()) {
818 auto itemClipRect = inputMethod->inputItemClipRectangle();
819 selectionRect.setLeft(itemClipRect.left());
820 selectionRect.setRight(itemClipRect.right());
822 return selectionRect.toCGRect();
\macro QT_RESTRICTED_CAST_FROM_ASCII
Q_FORWARD_DECLARE_OBJC_CLASS(NSString)