1/**************************************************************************** 2** 3** Copyright (C) 2018 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the plugins of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40// This file is included from qnsview.mm, and only used to organize the code 41 42@implementation QNSView (ComplexTextAPI) 43 44- (void)cancelComposingText 45{ 46 if (m_composingText.isEmpty()) 47 return; 48 49 if (m_composingFocusObject) { 50 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 51 if (QCoreApplication::sendEvent(m_composingFocusObject, &queryEvent)) { 52 if (queryEvent.value(Qt::ImEnabled).toBool()) { 53 QInputMethodEvent e; 54 QCoreApplication::sendEvent(m_composingFocusObject, &e); 55 } 56 } 57 } 58 59 m_composingText.clear(); 60 m_composingFocusObject = nullptr; 61} 62 63- (void)unmarkText 64{ 65 if (!m_composingText.isEmpty()) { 66 if (QObject *fo = m_platformWindow->window()->focusObject()) { 67 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 68 if (QCoreApplication::sendEvent(fo, &queryEvent)) { 69 if (queryEvent.value(Qt::ImEnabled).toBool()) { 70 QInputMethodEvent e; 71 e.setCommitString(m_composingText); 72 QCoreApplication::sendEvent(fo, &e); 73 } 74 } 75 } 76 } 77 m_composingText.clear(); 78 m_composingFocusObject = nullptr; 79} 80 81@end 82 83@implementation QNSView (ComplexText) 84 85- (void)insertNewline:(id)sender 86{ 87 Q_UNUSED(sender); 88 m_resendKeyEvent = true; 89} 90 91- (void)doCommandBySelector:(SEL)aSelector 92{ 93 [self tryToPerform:aSelector with:self]; 94} 95 96- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange 97{ 98 Q_UNUSED(replacementRange) 99 100 if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { 101 // don't send input method events for simple text input (let handleKeyEvent send key events instead) 102 return; 103 } 104 105 QString commitString; 106 if ([aString length]) { 107 if ([aString isKindOfClass:[NSAttributedString class]]) { 108 commitString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); 109 } else { 110 commitString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); 111 }; 112 } 113 if (QObject *fo = m_platformWindow->window()->focusObject()) { 114 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 115 if (QCoreApplication::sendEvent(fo, &queryEvent)) { 116 if (queryEvent.value(Qt::ImEnabled).toBool()) { 117 QInputMethodEvent e; 118 e.setCommitString(commitString); 119 QCoreApplication::sendEvent(fo, &e); 120 // prevent handleKeyEvent from sending a key event 121 m_sendKeyEvent = false; 122 } 123 } 124 } 125 126 m_composingText.clear(); 127 m_composingFocusObject = nullptr; 128} 129 130- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange 131{ 132 Q_UNUSED(replacementRange) 133 QString preeditString; 134 135 QList<QInputMethodEvent::Attribute> attrs; 136 attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, selectedRange.location + selectedRange.length, 1, QVariant()); 137 138 if ([aString isKindOfClass:[NSAttributedString class]]) { 139 // Preedit string has attribution 140 preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); 141 int composingLength = preeditString.length(); 142 int index = 0; 143 // Create attributes for individual sections of preedit text 144 while (index < composingLength) { 145 NSRange effectiveRange; 146 NSRange range = NSMakeRange(index, composingLength-index); 147 NSDictionary *attributes = [aString attributesAtIndex:index 148 longestEffectiveRange:&effectiveRange 149 inRange:range]; 150 NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; 151 if (underlineStyle) { 152 QColor clr (Qt::black); 153 NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; 154 if (color) { 155 clr = qt_mac_toQColor(color); 156 } 157 QTextCharFormat format; 158 format.setFontUnderline(true); 159 format.setUnderlineColor(clr); 160 attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 161 effectiveRange.location, 162 effectiveRange.length, 163 format); 164 } 165 index = effectiveRange.location + effectiveRange.length; 166 } 167 } else { 168 // No attributes specified, take only the preedit text. 169 preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); 170 } 171 172 if (attrs.isEmpty()) { 173 QTextCharFormat format; 174 format.setFontUnderline(true); 175 attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 176 0, preeditString.length(), format); 177 } 178 179 m_composingText = preeditString; 180 181 if (QObject *fo = m_platformWindow->window()->focusObject()) { 182 m_composingFocusObject = fo; 183 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 184 if (QCoreApplication::sendEvent(fo, &queryEvent)) { 185 if (queryEvent.value(Qt::ImEnabled).toBool()) { 186 QInputMethodEvent e(preeditString, attrs); 187 QCoreApplication::sendEvent(fo, &e); 188 // prevent handleKeyEvent from sending a key event 189 m_sendKeyEvent = false; 190 } 191 } 192 } 193} 194 195- (BOOL)hasMarkedText 196{ 197 return (m_composingText.isEmpty() ? NO: YES); 198} 199 200- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange 201{ 202 Q_UNUSED(actualRange) 203 QObject *fo = m_platformWindow->window()->focusObject(); 204 if (!fo) 205 return nil; 206 QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); 207 if (!QCoreApplication::sendEvent(fo, &queryEvent)) 208 return nil; 209 if (!queryEvent.value(Qt::ImEnabled).toBool()) 210 return nil; 211 212 QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); 213 if (selectedText.isEmpty()) 214 return nil; 215 216 QCFString string(selectedText.mid(aRange.location, aRange.length)); 217 const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); 218 return [[[NSAttributedString alloc] initWithString:const_cast<NSString *>(tmpString)] autorelease]; 219} 220 221- (NSRange)markedRange 222{ 223 NSRange range; 224 if (!m_composingText.isEmpty()) { 225 range.location = 0; 226 range.length = m_composingText.length(); 227 } else { 228 range.location = NSNotFound; 229 range.length = 0; 230 } 231 return range; 232} 233 234- (NSRange)selectedRange 235{ 236 NSRange selectedRange = {0, 0}; 237 238 QObject *fo = m_platformWindow->window()->focusObject(); 239 if (!fo) 240 return selectedRange; 241 QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); 242 if (!QCoreApplication::sendEvent(fo, &queryEvent)) 243 return selectedRange; 244 if (!queryEvent.value(Qt::ImEnabled).toBool()) 245 return selectedRange; 246 247 QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); 248 249 if (!selectedText.isEmpty()) { 250 selectedRange.location = 0; 251 selectedRange.length = selectedText.length(); 252 } 253 return selectedRange; 254} 255 256- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange 257{ 258 Q_UNUSED(aRange) 259 Q_UNUSED(actualRange) 260 261 QObject *fo = m_platformWindow->window()->focusObject(); 262 if (!fo) 263 return NSZeroRect; 264 265 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 266 if (!QCoreApplication::sendEvent(fo, &queryEvent)) 267 return NSZeroRect; 268 if (!queryEvent.value(Qt::ImEnabled).toBool()) 269 return NSZeroRect; 270 271 // The returned rect is always based on the internal cursor. 272 QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); 273 mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); 274 return QCocoaScreen::mapToNative(mr); 275} 276 277- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint 278{ 279 // We don't support cursor movements using mouse while composing. 280 Q_UNUSED(aPoint); 281 return NSNotFound; 282} 283 284- (NSArray<NSString *> *)validAttributesForMarkedText 285{ 286 if (!m_platformWindow) 287 return nil; 288 289 if (m_platformWindow->window() != QGuiApplication::focusWindow()) 290 return nil; 291 292 QObject *fo = m_platformWindow->window()->focusObject(); 293 if (!fo) 294 return nil; 295 296 QInputMethodQueryEvent queryEvent(Qt::ImEnabled); 297 if (!QCoreApplication::sendEvent(fo, &queryEvent)) 298 return nil; 299 if (!queryEvent.value(Qt::ImEnabled).toBool()) 300 return nil; 301 302 // Support only underline color/style. 303 return @[NSUnderlineColorAttributeName, NSUnderlineStyleAttributeName]; 304} 305 306- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification 307{ 308 Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) 309 if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) { 310 if (QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext())) 311 ic->updateLocale(); 312 } 313} 314 315@end 316