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 auto *currentEvent = NSApp.currentEvent;
56 NSString *eventText = currentEvent.type == NSEventTypeKeyDown
57 || currentEvent.type == NSEventTypeKeyUp
58 ? currentEvent.characters : nil;
60 if ([string isEqualToString:eventText]) {
63 qCDebug(lcQpaKeys) <<
"Ignoring text insertion for simple text";
64 m_sendKeyEvent =
true;
69 if (queryInputMethod(self.focusObject)) {
70 QInputMethodEvent inputMethodEvent;
72 QString commitString = QString::fromNSString(string);
75 replacementRange = [self sanitizeReplacementRange:replacementRange];
79 auto [replaceFrom, replaceLength] = [self inputMethodRangeForRange:replacementRange];
81 if (replaceFrom == NSNotFound) {
82 qCWarning(lcQpaKeys) <<
"Failed to compute valid replacement range for text insertion";
83 inputMethodEvent.setCommitString(commitString);
85 qCDebug(lcQpaKeys) <<
"Replacing from" << replaceFrom <<
"with length" << replaceLength
86 <<
"based on replacement range" << replacementRange;
87 inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength);
90 QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
93 m_composingText.clear();
94 m_composingFocusObject =
nullptr;
97- (
void)insertNewline:(id)sender
101 if (!m_platformWindow)
127 KeyEvent newlineEvent(m_currentlyInterpretedKeyEvent ?
128 m_currentlyInterpretedKeyEvent : NSApp.currentEvent);
129 newlineEvent.type = QEvent::KeyPress;
131 const bool isEnter = newlineEvent.modifiers & Qt::KeypadModifier;
132 newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return;
133 newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode)
134 : QLatin1Char(kReturnCharCode);
135 newlineEvent.nativeVirtualKey = isEnter ? quint32(kVK_ANSI_KeypadEnter)
136 : quint32(kVK_Return);
138 qCDebug(lcQpaKeys) <<
"Inserting newline via" << newlineEvent;
139 newlineEvent.sendWindowSystemEvent(m_platformWindow->window());
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164- (
void)setMarkedText:(id)text selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
166 qCDebug(lcQpaKeys).nospace() <<
"Marking \"" << text <<
"\""
167 <<
" with selected range " << selectedRange
168 <<
", replacing range " << replacementRange;
170 const bool isAttributedString = [text isKindOfClass:NSAttributedString.
class];
171 QString preeditString = QString::fromNSString([self stringForText:text]);
173 QList<QInputMethodEvent::Attribute> preeditAttributes;
181 const bool showCursor = !selectedRange.length;
182 preeditAttributes << QInputMethodEvent::Attribute(
183 QInputMethodEvent::Cursor, selectedRange.location, showCursor);
191 QTextCharFormat selectionFormat;
192 auto *platformTheme = QGuiApplicationPrivate::platformTheme();
193 auto *systemPalette = platformTheme->palette();
194 selectionFormat.setBackground(systemPalette->color(QPalette::Highlight));
195 preeditAttributes << QInputMethodEvent::Attribute(
196 QInputMethodEvent::TextFormat,
197 selectedRange.location, selectedRange.length,
201 int composingLength = preeditString.length();
202 while (index < composingLength) {
203 NSRange range = NSMakeRange(index, composingLength - index);
205 static NSDictionary *defaultMarkedTextAttributes = []{
206 NSTextView *textView = [[NSTextView
new] autorelease];
207 return [textView.markedTextAttributes retain];
210 NSDictionary *attributes = isAttributedString
211 ? [text attributesAtIndex:index longestEffectiveRange:&range inRange:range]
212 : defaultMarkedTextAttributes;
214 qCDebug(lcQpaKeys) <<
"Decorating range" << range <<
"based on" << attributes;
215 QTextCharFormat format;
217 if (NSNumber *underlineStyle = attributes[NSUnderlineStyleAttributeName]) {
218 format.setFontUnderline(
true);
219 NSUnderlineStyle style = underlineStyle.integerValue;
220 if (style & NSUnderlineStylePatternDot)
221 format.setUnderlineStyle(QTextCharFormat::DotLine);
222 else if (style & NSUnderlineStylePatternDash)
223 format.setUnderlineStyle(QTextCharFormat::DashUnderline);
224 else if (style & NSUnderlineStylePatternDashDot)
225 format.setUnderlineStyle(QTextCharFormat::DashDotLine);
226 if (style & NSUnderlineStylePatternDashDotDot)
227 format.setUnderlineStyle(QTextCharFormat::DashDotDotLine);
229 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
235 if (NSColor *underlineColor = attributes[NSUnderlineColorAttributeName])
236 format.setUnderlineColor(qt_mac_toQColor(underlineColor));
237 if (NSColor *foregroundColor = attributes[NSForegroundColorAttributeName])
238 format.setForeground(qt_mac_toQColor(foregroundColor));
239 if (NSColor *backgroundColor = attributes[NSBackgroundColorAttributeName])
240 format.setBackground(qt_mac_toQColor(backgroundColor));
242 if (format != QTextCharFormat()) {
243 preeditAttributes << QInputMethodEvent::Attribute(
244 QInputMethodEvent::TextFormat, range.location, range.length, format);
247 index = range.location + range.length;
251 replacementRange = [self sanitizeReplacementRange:replacementRange];
255 auto [replaceFrom, replaceLength] = [self inputMethodRangeForRange:replacementRange];
258 m_composingText = preeditString;
260 if (QObject *focusObject = self.focusObject) {
261 m_composingFocusObject = focusObject;
262 if (queryInputMethod(focusObject)) {
263 QInputMethodEvent event(preeditString, preeditAttributes);
264 if (replaceLength > 0) {
268 qCDebug(lcQpaKeys) <<
"Replacing from" << replaceFrom <<
"with length"
269 << replaceLength <<
"based on replacement range" << replacementRange;
270 event.setCommitString(QString(), replaceFrom, replaceLength);
272 QCoreApplication::sendEvent(focusObject, &event);
277- (NSArray<NSString *> *)validAttributesForMarkedText
280 NSUnderlineColorAttributeName,
281 NSUnderlineStyleAttributeName,
282 NSForegroundColorAttributeName,
283 NSBackgroundColorAttributeName
289 return !m_composingText.isEmpty();
293
294
295
296
297
298
299
300- (NSRange)markedRange
302 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) {
303 int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
315 return NSMakeRange(absoluteCursorPosition, m_composingText.length());
317 return {NSNotFound, 0};
322
323
324
325
326
327
328
333 qCDebug(lcQpaKeys) <<
"Unmarking" << m_composingText
334 <<
"for focus object" << m_composingFocusObject;
336 if (!m_composingText.isEmpty()) {
337 QObject *focusObject = self.focusObject;
338 if (queryInputMethod(focusObject)) {
340 e.setCommitString(m_composingText);
341 QCoreApplication::sendEvent(focusObject, &e);
345 m_composingText.clear();
346 m_composingFocusObject =
nullptr;
350
351
352
353
354
355
356- (
void)cancelComposingText
358 if (m_composingText.isEmpty())
361 qCDebug(lcQpaKeys) <<
"Canceling composition" << m_composingText
362 <<
"for focus object" << m_composingFocusObject;
364 if (queryInputMethod(m_composingFocusObject)) {
366 QCoreApplication::sendEvent(m_composingFocusObject, &e);
369 m_composingText.clear();
370 m_composingFocusObject =
nullptr;
375- (
void)doCommandBySelector:(SEL)selector
383 qCDebug(lcQpaKeys) <<
"Trying to perform command" << selector;
384 if (![self tryToPerform:selector with:self]) {
385 m_sendKeyEvent =
true;
387 if (![NSStringFromSelector(selector) hasPrefix:@
"insert"]) {
395 m_sendKeyEventWithoutText =
true;
403
404
405
406
407
408- (NSRange)selectedRange
410 if (
auto queryResult = queryInputMethod(self.focusObject,
411 Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) {
418 int cursorPosition = queryResult.value(Qt::ImCursorPosition).toInt();
419 int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
420 int absoluteOffset = absoluteCursorPosition - cursorPosition;
422 int anchorPosition = absoluteOffset + queryResult.value(Qt::ImAnchorPosition).toInt();
423 int selectionStart = anchorPosition >= absoluteCursorPosition ? absoluteCursorPosition : anchorPosition;
424 int selectionEnd = selectionStart == anchorPosition ? absoluteCursorPosition : anchorPosition;
425 int selectionLength = selectionEnd - selectionStart;
435 return NSMakeRange(selectionStart, selectionLength);
437 return {NSNotFound, 0};
442
443
444
445
446
447
448
449
450
451
452
453
454
455- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
457 if (
auto queryResult = queryInputMethod(self.focusObject,
458 Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) {
459 const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
460 const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString();
461 const QString textAfterCursor = queryResult.value(Qt::ImTextAfterCursor).toString();
466 const QString availableText = textBeforeCursor + m_composingText + textAfterCursor;
467 const NSRange availableRange = NSMakeRange(absoluteCursorPosition - textBeforeCursor.length(),
468 availableText.length());
470 const NSRange intersectedRange = NSIntersectionRange(range, availableRange);
472 *actualRange = intersectedRange;
474 if (!intersectedRange.length)
477 NSString *substring = QStringView(availableText).mid(
478 intersectedRange.location - availableRange.location,
479 intersectedRange.length).toNSString();
481 return [[[NSAttributedString alloc] initWithString:substring] autorelease];
489
490
491
492
493
494
495
496
497
498
499
500- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
503 Q_UNUSED(actualRange);
505 QWindow *window = m_platformWindow ? m_platformWindow->window() :
nullptr;
506 if (window && queryInputMethod(window->focusObject())) {
508 qCWarning(lcQpaKeys) <<
"Can't satisfy firstRectForCharacterRange for" << range;
509 QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect();
510 cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft()));
511 return QCocoaScreen::mapToNative(cursorRect);
517- (
NSUInteger)characterIndexForPoint:(NSPoint)point
525
526
527
528
529
537 auto level = m_platformWindow ? m_platformWindow->nativeWindow().level
538 : NSNormalWindowLevel;
547 return qMax(level, NSPopUpMenuWindowLevel);
553
554
555
556
557
558
559
560
561
562
563- (NSRange)sanitizeReplacementRange:(NSRange)range
565 if (range.location != NSNotFound)
571 const auto markedRange = [self markedRange];
572 const auto selectedRange = [self selectedRange];
574 if (markedRange.length)
576 else if (selectedRange.length)
577 return selectedRange;
584
585
586
587
588
589- (std::pair<
long long,
long long>)inputMethodRangeForRange:(NSRange)replacementRange
591 long long replaceFrom = replacementRange.location;
592 long long replaceLength = replacementRange.length;
594 const auto markedRange = [self markedRange];
595 const auto selectedRange = [self selectedRange];
597 if (markedRange.length && selectedRange.length) {
599 qCWarning(lcQpaKeys) <<
"Got both markedRange" << markedRange
600 <<
"and selectedRange" << selectedRange;
603 if (markedRange.length) {
607 replaceLength -= markedRange.length;
611 replaceFrom -= markedRange.location;
612 }
else if (selectedRange.length) {
613 if (!NSEqualRanges(NSIntersectionRange(replacementRange, selectedRange), selectedRange)) {
614 qCWarning(lcQpaKeys) <<
"Replacement range" << replacementRange
615 <<
"is a subset of selection" << selectedRange;
625 replaceLength -= selectedRange.length;
631 replaceFrom -= selectedRange.location;
632 }
else if (markedRange.location != NSNotFound) {
635 replaceFrom -= markedRange.location;
642 replaceLength = qMax(0ll, replaceLength);
644 return {replaceFrom, replaceLength};
649 return [text isKindOfClass:NSAttributedString.
class] ? [text string] : text;
654@implementation QNSView (ServicesMenu)
662- (id)validRequestorForSendType:(NSPasteboardType)sendType returnType:(NSPasteboardType)returnType
664 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImReadOnly | Qt::ImCurrentSelection)) {
665 bool canWriteToPasteboard =
false;
666 bool canReadFromPastboard =
false;
668 auto currentSelection = queryResult.value(Qt::ImCurrentSelection);
669 if (
auto *mimeData = currentSelection.value<QMimeData*>()) {
672 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
673 auto availableConverters = QMacMimeRegistry::all(scope);
674 auto sendUti = [self utiForPasteboardType:sendType];
675 auto returnUti = [self utiForPasteboardType:returnType];
676 const auto mimeFormats = mimeData->formats();
677 for (
const auto *c : availableConverters) {
678 if (mimeFormats.contains(c->mimeForUti(sendUti)))
679 canWriteToPasteboard =
true;
680 if (mimeFormats.contains(c->mimeForUti(returnUti)))
681 canReadFromPastboard =
true;
682 if (canWriteToPasteboard && canReadFromPastboard)
686 canWriteToPasteboard = [sendType isEqualToString:NSPasteboardTypeString]
687 && !currentSelection.toString().isEmpty();
688 canReadFromPastboard = [returnType isEqualToString:NSPasteboardTypeString]
689 && !queryResult.value(Qt::ImReadOnly).toBool();
692 if (!((sendType && !canWriteToPasteboard) || (returnType && !canReadFromPastboard))) {
693 qCDebug(lcQpaServices) <<
"Accepting service interaction for send" << sendType <<
"and receive" << returnType;
698 return [super validRequestorForSendType:sendType returnType:returnType];
701- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray<NSPasteboardType> *)types
703 bool didWrite =
false;
705 if (
auto queryResult = queryInputMethod(self.focusObject, Qt::ImCurrentSelection)) {
706 auto currentSelection = queryResult.value(Qt::ImCurrentSelection);
707 if (
auto *mimeData = currentSelection.value<QMimeData*>()) {
708 auto mimeFormats = mimeData->formats();
709 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
710 auto availableConverters = QMacMimeRegistry::all(scope);
711 for (NSPasteboardType type in types) {
712 auto uti = [self utiForPasteboardType:type];
714 qCWarning(lcQpaServices) <<
"Did not find UTI for type" << type;
717 for (
const auto *converter : availableConverters) {
718 auto mime = converter->mimeForUti(uti);
719 if (mimeFormats.contains(mime)) {
720 auto utiDataList = converter->convertFromMime(mime,
721 mimeData->data(mime), uti);
722 if (utiDataList.isEmpty())
724 auto utiData = utiDataList.first();
725 qCDebug(lcQpaServices) <<
"Writing" << utiData <<
"to service pasteboard"
726 <<
"with UTI" << uti <<
"for type" << type <<
"based on mime" << mime;
727 didWrite |= [pasteboard setData:utiData.toNSData() forType:type];
735 if (!didWrite && ([types containsObject:NSPasteboardTypeString]
736 || QT_IGNORE_DEPRECATIONS([types containsObject:NSStringPboardType]))) {
737 auto selectedText = currentSelection.toString();
738 qCDebug(lcQpaServices) <<
"Writing" << selectedText <<
"to service pasteboard"
739 <<
"as pain text" <<
"for type" << NSPasteboardTypeString;
740 didWrite |= [pasteboard writeObjects:@[ selectedText.toNSString() ]];
747- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard
749 if (queryInputMethod(self.focusObject)) {
750 auto scope = QUtiMimeConverter::HandlerScopeFlag::Clipboard;
751 QMacPasteboard macPasteboard(CFStringRef(pasteboard.name), scope);
752 auto *mimeData = macPasteboard.mimeData();
753 if (mimeData->formats().isEmpty()) {
754 qCWarning(lcQpaServices) <<
"Failed to resolve mime data from" << pasteboard.types;
758 qCDebug(lcQpaServices) <<
"Replacing selected range" << [self selectedRange]
759 <<
"with mime data" << [&]() {
760 QMap<QString, QByteArray> formatMap;
761 for (
const auto &format : mimeData->formats())
762 formatMap.insert(format, mimeData->data(format));
764 }() <<
"from service pasteboard" << pasteboard.name;
766 QList<QInputMethodEvent::Attribute> attributes;
767 attributes << QInputMethodEvent::Attribute(
768 QInputMethodEvent::MimeData,
769 0, 0, QVariant::fromValue(mimeData));
771 QInputMethodEvent inputMethodEvent(QString(), attributes);
775 inputMethodEvent.setCommitString(mimeData->text());
776 QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
783- (
QString)utiForPasteboardType:(NSPasteboardType)pasteboardType
788 UTType *uttype = [UTType typeWithIdentifier:pasteboardType];
794 uttype = [UTType typeWithTag:pasteboardType
795 tagClass:QT_IGNORE_DEPRECATIONS((NSString*)kUTTagClassNSPboardType)
796 conformingToType:nil];
798 return QString::fromNSString(uttype.identifier);
803#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000
)
804@implementation QNSView (ContentSelectionInfo)
807
808
809
810
811- (NSRect)selectionAnchorRect
813 if (queryInputMethod(self.focusObject)) {
816 const auto *inputMethod = qApp->inputMethod();
817 auto cursorRect = inputMethod->cursorRectangle();
818 auto anchorRect = inputMethod->anchorRectangle();
819 auto selectionRect = cursorRect.united(anchorRect);
820 if (cursorRect.top() != anchorRect.top()) {
824 auto itemClipRect = inputMethod->inputItemClipRectangle();
825 selectionRect.setLeft(itemClipRect.left());
826 selectionRect.setRight(itemClipRect.right());
828 return selectionRect.toCGRect();
Q_FORWARD_DECLARE_OBJC_CLASS(NSString)