Blender V4.3
GHOST_WindowViewCocoa.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5/* The Carbon API is still needed to check if the Input Source (Input Method or IME) is valid. */
6#ifdef WITH_INPUT_IME
7# import <Carbon/Carbon.h>
8#endif
9
10/* NSView subclass for drawing and handling input.
11 *
12 * COCOA_VIEW_BASE_CLASS will be either NSView or NSOpenGLView depending if
13 * we use a Metal or OpenGL layer for drawing in the view. We use macros
14 * to defined classes for each case, so we don't have to duplicate code as
15 * Objective-C does not have multiple inheritance. */
16
17// We need to subclass it in order to give Cocoa the feeling key events are trapped
18@interface COCOA_VIEW_CLASS : COCOA_VIEW_BASE_CLASS <NSTextInputClient>
19{
20 bool composing;
21 NSString *composing_text;
23#ifdef WITH_INPUT_IME
24 struct {
25 GHOST_ImeStateFlagCocoa state_flag;
26 NSRect candidate_window_position;
27
28 /* Event data. */
30 std::string result;
31 std::string composite;
32 std::string combined_result;
33 } ime;
34#endif
35}
36
37@property(nonatomic, readonly, assign) GHOST_SystemCocoa *systemCocoa;
38@property(nonatomic, readonly, assign) GHOST_WindowCocoa *windowCocoa;
40- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
41 windowCocoa:(GHOST_WindowCocoa *)winCocoa
42 frame:(NSRect)frameRect;
43
44#ifdef WITH_INPUT_IME
45- (void)beginIME:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h completed:(bool)completed;
46
47- (void)endIME;
48#endif
49
50@end
51
52@implementation COCOA_VIEW_CLASS
53
54@synthesize systemCocoa = m_systemCocoa;
55@synthesize windowCocoa = m_windowCocoa;
56
57- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
58 windowCocoa:(GHOST_WindowCocoa *)winCocoa
59 frame:(NSRect)frameRect
60{
61 self = [super init];
62
63 if (self) {
64 m_systemCocoa = sysCocoa;
65 m_windowCocoa = winCocoa;
66
67 composing = false;
68 composing_text = nil;
69
70#ifdef WITH_INPUT_IME
71 ime.state_flag = 0;
72 ime.candidate_window_position = NSZeroRect;
73 ime.event.cursor_position = -1;
74 ime.event.target_start = -1;
75 ime.event.target_end = -1;
76
77 /* Register a function to be executed when Input Method is changed using
78 * 'Control + Space' or language-specific keys (such as 'Eisu / Kana' key for Japanese). */
79 @autoreleasepool {
80 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
81 [center addObserver:self
82 selector:@selector(ImeDidChangeCallback:)
83 name:NSTextInputContextKeyboardSelectionDidChangeNotification
84 object:nil];
85 }
86#endif
87 }
88
89 return self;
90}
91
92- (BOOL)acceptsFirstResponder
93{
94 return YES;
95}
96
97- (BOOL)acceptsFirstMouse:(NSEvent *)event
98{
99 return YES;
100}
101
102// The trick to prevent Cocoa from complaining (beeping)
103- (void)keyDown:(NSEvent *)event
104{
105#ifdef WITH_INPUT_IME
106 [self checkKeyCodeIsControlChar:event];
107 const bool ime_process = [self isProcessedByIme];
108#else
109 const bool ime_process = false;
110#endif
111
112 if (!ime_process) {
113 m_systemCocoa->handleKeyEvent(event);
114 }
115
116 /* Start or continue composing? */
117 if ([[event characters] length] == 0 || [[event charactersIgnoringModifiers] length] == 0 ||
118 composing || ime_process)
119 {
120 composing = YES;
121
122 /* Interpret event to call insertText. */
123 @autoreleasepool {
124 [self interpretKeyEvents:[NSArray arrayWithObject:event]]; /* Calls insertText. */
125 }
126
127#ifdef WITH_INPUT_IME
128 // For Korean input, control characters are also processed by handleKeyEvent.
129 const int controlCharForKorean = (GHOST_IME_COMPOSITION_EVENT | GHOST_IME_RESULT_EVENT |
130 GHOST_IME_KEY_CONTROL_CHAR);
131 if (((ime.state_flag & controlCharForKorean) == controlCharForKorean)) {
132 m_systemCocoa->handleKeyEvent(event);
133 }
134
135 ime.state_flag &= ~(GHOST_IME_COMPOSITION_EVENT | GHOST_IME_RESULT_EVENT);
136
137 ime.combined_result.clear();
138#endif
139
140 return;
141 }
142}
143
144#define HANDLE_KEY_EVENT(eventType) \
145 -(void)eventType : (NSEvent *)event \
146 { \
147 m_systemCocoa->handleKeyEvent(event); \
148 }
149
150#define HANDLE_MOUSE_EVENT(eventType) \
151 -(void)eventType : (NSEvent *)event \
152 { \
153 m_systemCocoa->handleMouseEvent(event); \
154 }
155
156#define HANDLE_TABLET_EVENT(eventType) \
157 -(void)eventType : (NSEvent *)event \
158 { \
159 m_systemCocoa->handleMouseEvent(event); \
160 }
161
162HANDLE_KEY_EVENT(keyUp)
163HANDLE_KEY_EVENT(flagsChanged)
164
165HANDLE_MOUSE_EVENT(mouseDown)
166HANDLE_MOUSE_EVENT(mouseUp)
167HANDLE_MOUSE_EVENT(rightMouseDown)
168HANDLE_MOUSE_EVENT(rightMouseUp)
169HANDLE_MOUSE_EVENT(mouseMoved)
170HANDLE_MOUSE_EVENT(mouseDragged)
171HANDLE_MOUSE_EVENT(rightMouseDragged)
172HANDLE_MOUSE_EVENT(scrollWheel)
173HANDLE_MOUSE_EVENT(otherMouseDown)
174HANDLE_MOUSE_EVENT(otherMouseUp)
175HANDLE_MOUSE_EVENT(otherMouseDragged)
176HANDLE_MOUSE_EVENT(magnifyWithEvent)
177HANDLE_MOUSE_EVENT(smartMagnifyWithEvent)
178HANDLE_MOUSE_EVENT(rotateWithEvent)
179
180HANDLE_TABLET_EVENT(tabletPoint)
181HANDLE_TABLET_EVENT(tabletProximity)
182
183- (BOOL)isOpaque
184{
185 return YES;
186}
187
188- (void)drawRect:(NSRect)rect
189{
190 if ([self inLiveResize]) {
191 /* Don't redraw while in live resize */
192 }
193 else {
194 [super drawRect:rect];
195 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowUpdate, m_windowCocoa);
196
197 /* For some cases like entering full-screen we need to redraw immediately
198 * so our window does not show blank during the animation */
199 if (m_windowCocoa->getImmediateDraw()) {
200 m_systemCocoa->dispatchEvents();
201 }
202 }
203}
204
205/* Text input. */
206
207- (void)composing_free
208{
209 composing = NO;
210
211 if (composing_text) {
212 [composing_text release];
213 composing_text = nil;
214 }
215}
216
217// Processes the Result String sent from the Input Method.
218- (void)insertText:(id)chars replacementRange:(NSRange)replacementRange
219{
220 [self composing_free];
221
222#ifdef WITH_INPUT_IME
223 if (ime.state_flag & GHOST_IME_ENABLED) {
224 if (!(ime.state_flag & GHOST_IME_COMPOSING)) {
225 [self processImeEvent:GHOST_kEventImeCompositionStart];
226 }
227
228 /* For Chinese and Korean input, insertText may be executed twice with a single keyDown. */
229 if (ime.state_flag & GHOST_IME_RESULT_EVENT) {
230 ime.combined_result += [self convertNSString:chars];
231 }
232 else {
233 ime.combined_result = [self convertNSString:chars];
234 }
235
236 [self setImeResult:ime.combined_result];
237
238 /* For Korean input, both "Result Event" and "Composition Event"
239 * can occur in a single keyDown. */
240 if (![self ime_did_composition]) {
241 [self processImeEvent:GHOST_kEventImeComposition];
242 }
243 ime.state_flag |= GHOST_IME_RESULT_EVENT;
244
245 [self processImeEvent:GHOST_kEventImeCompositionEnd];
246 ime.state_flag &= ~GHOST_IME_COMPOSING;
247 }
248#endif
249}
250
251/* Processes the Composition String sent from the Input Method. */
252- (void)setMarkedText:(id)chars
253 selectedRange:(NSRange)range
254 replacementRange:(NSRange)replacementRange
255{
256 [self composing_free];
257
258 if ([chars length] == 0) {
259#ifdef WITH_INPUT_IME
260 /* Processes when the last Composition String is deleted. */
261 if (ime.state_flag & GHOST_IME_COMPOSING) {
262 [self setImeResult:std::string()];
263 [self processImeEvent:GHOST_kEventImeComposition];
264 [self processImeEvent:GHOST_kEventImeCompositionEnd];
265 ime.state_flag &= ~GHOST_IME_COMPOSING;
266 }
267#endif
268
269 return;
270 }
271
272 /* Start composing. */
273 composing = YES;
274 composing_text = [chars copy];
275
276 /* Chars of markedText by Input Method is an instance of NSAttributedString */
277 if ([chars isKindOfClass:[NSAttributedString class]]) {
278 composing_text = [[chars string] copy];
279 }
280
281 /* If empty, cancel. */
282 if ([composing_text length] == 0) {
283 [self composing_free];
284 }
285
286#ifdef WITH_INPUT_IME
287 if (ime.state_flag & GHOST_IME_ENABLED) {
288 if (!(ime.state_flag & GHOST_IME_COMPOSING)) {
289 ime.state_flag |= GHOST_IME_COMPOSING;
290 [self processImeEvent:GHOST_kEventImeCompositionStart];
291 }
292
293 [self setImeComposition:composing_text selectedRange:range];
294
295 /* For Korean input, setMarkedText may be executed twice with a single keyDown. */
296 if (![self ime_did_composition]) {
297 ime.state_flag |= GHOST_IME_COMPOSITION_EVENT;
298 [self processImeEvent:GHOST_kEventImeComposition];
299 }
300 }
301#endif
302}
303
304- (void)unmarkText
305{
306 [self composing_free];
307}
308
309- (BOOL)hasMarkedText
310{
311 return (composing) ? YES : NO;
312}
313
314- (void)doCommandBySelector:(SEL)selector
315{
316}
317
318- (BOOL)isComposing
319{
320 return composing;
321}
322
323- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
324 actualRange:(NSRangePointer)actualRange
325{
326 return [[[NSAttributedString alloc] init] autorelease];
327}
328
329- (NSRange)markedRange
330{
331 unsigned int length = (composing_text) ? [composing_text length] : 0;
332
333 if (composing) {
334 return NSMakeRange(0, length);
335 }
336
337 return NSMakeRange(NSNotFound, 0);
338}
339
340- (NSRange)selectedRange
341{
342 unsigned int length = (composing_text) ? [composing_text length] : 0;
343 return NSMakeRange(0, length);
344}
345
346// Specify the position where the Chinese and Japanese candidate windows are displayed.
347- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
348{
349#ifdef WITH_INPUT_IME
350 if (ime.state_flag & GHOST_IME_ENABLED) {
351 return ime.candidate_window_position;
352 }
353#endif
354 return NSZeroRect;
355}
356
357- (NSUInteger)characterIndexForPoint:(NSPoint)point
358{
359 return NSNotFound;
360}
361
362- (NSArray *)validAttributesForMarkedText
363{
364 return [NSArray array];
365}
366
367#ifdef WITH_INPUT_IME
368- (void)checkImeEnabled
369{
370 ime.state_flag &= ~GHOST_IME_ENABLED;
371
372 if (ime.state_flag & GHOST_IME_INPUT_FOCUSED) {
373 /* Since there are no functions in Cocoa API,
374 * we will use the functions in the Carbon API. */
375 TISInputSourceRef currentKeyboardInputSource = TISCopyCurrentKeyboardInputSource();
376 bool ime_enabled = !CFBooleanGetValue((CFBooleanRef)TISGetInputSourceProperty(
377 currentKeyboardInputSource, kTISPropertyInputSourceIsASCIICapable));
378 CFRelease(currentKeyboardInputSource);
379
380 if (ime_enabled) {
381 ime.state_flag |= GHOST_IME_ENABLED;
382 return;
383 }
384 }
385 return;
386}
387
388- (void)ImeDidChangeCallback:(NSNotification *)notification
389{
390 [self checkImeEnabled];
391}
392
393- (void)setImeCandidateWinPos:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h
394{
395 int32_t outX, outY;
396 m_windowCocoa->clientToScreen(x, y, outX, outY);
397 ime.candidate_window_position = NSMakeRect((CGFloat)outX, (CGFloat)outY, (CGFloat)w, (CGFloat)h);
398}
399
400- (void)beginIME:(int32_t)x y:(int32_t)y w:(int32_t)w h:(int32_t)h completed:(bool)completed
401{
402 ime.state_flag |= GHOST_IME_INPUT_FOCUSED;
403 [self checkImeEnabled];
404 [self setImeCandidateWinPos:x y:y w:w h:h];
405}
406
407- (void)endIME
408{
409 ime.state_flag = 0;
410 ime.result.clear();
411 ime.composite.clear();
412
413 [self unmarkText];
414 @autoreleasepool {
415 [[NSTextInputContext currentInputContext] discardMarkedText];
416 }
417}
418
419- (void)processImeEvent:(GHOST_TEventType)imeEventType
420{
421 ime.event.result_len = (GHOST_TUserDataPtr)ime.result.size();
422 ime.event.result = (GHOST_TUserDataPtr)ime.result.c_str();
423 ime.event.composite_len = (GHOST_TUserDataPtr)ime.composite.size();
424 ime.event.composite = (GHOST_TUserDataPtr)ime.composite.c_str();
425
426 GHOST_Event *event = new GHOST_EventIME(
427 m_systemCocoa->getMilliSeconds(), imeEventType, m_windowCocoa, &ime.event);
428 m_systemCocoa->pushEvent(event);
429}
430
431- (std::string)convertNSString:(NSString *)inString
432{
433 @autoreleasepool {
434 std::string str(inString.UTF8String);
435 return str;
436 }
437}
438
439- (void)setImeComposition:(NSString *)inString selectedRange:(NSRange)range
440{
441 ime.composite = [self convertNSString:inString];
442
443 /* For Korean input, both "Result Event" and "Composition Event" can occur in a single keyDown.
444 */
445 if (!(ime.state_flag & GHOST_IME_RESULT_EVENT)) {
446 ime.result.clear();
447 }
448
449 /* The target string is equivalent to the string in selectedRange of setMarkedText.
450 * The cursor is displayed at the beginning of the target string. */
451 @autoreleasepool {
452 char *front_string = (char *)[[inString substringWithRange:NSMakeRange(0, range.location)]
453 UTF8String];
454 char *selected_string = (char *)[[inString substringWithRange:range] UTF8String];
455 ime.event.cursor_position = strlen(front_string);
456 ime.event.target_start = ime.event.cursor_position;
457 ime.event.target_end = ime.event.target_start + strlen(selected_string);
458 }
459}
460
461- (void)setImeResult:(std::string)result
462{
463 ime.result = result;
464 ime.composite.clear();
465 ime.event.cursor_position = -1;
466 ime.event.target_start = -1;
467 ime.event.target_end = -1;
468}
469
470- (void)checkKeyCodeIsControlChar:(NSEvent *)event
471{
472 ime.state_flag &= ~GHOST_IME_KEY_CONTROL_CHAR;
473
474 /* Don't use IME for command and ctrl key combinations, these are shortcuts. */
475 if (event.modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)) {
476 ime.state_flag |= GHOST_IME_KEY_CONTROL_CHAR;
477 return;
478 }
479
480 /* Don't use IME for these control keys. */
481 switch (event.keyCode) {
482 case kVK_ANSI_KeypadEnter:
483 case kVK_ANSI_KeypadClear:
484 case kVK_F1:
485 case kVK_F2:
486 case kVK_F3:
487 case kVK_F4:
488 case kVK_F5:
489 case kVK_F6:
490 case kVK_F7:
491 case kVK_F8:
492 case kVK_F9:
493 case kVK_F10:
494 case kVK_F11:
495 case kVK_F12:
496 case kVK_F13:
497 case kVK_F14:
498 case kVK_F15:
499 case kVK_F16:
500 case kVK_F17:
501 case kVK_F18:
502 case kVK_F19:
503 case kVK_F20:
504 case kVK_UpArrow:
505 case kVK_DownArrow:
506 case kVK_LeftArrow:
507 case kVK_RightArrow:
508 case kVK_Return:
509 case kVK_Delete:
510 case kVK_ForwardDelete:
511 case kVK_Escape:
512 case kVK_Tab:
513 case kVK_Home:
514 case kVK_End:
515 case kVK_PageUp:
516 case kVK_PageDown:
517 case kVK_VolumeUp:
518 case kVK_VolumeDown:
519 case kVK_Mute:
520 ime.state_flag |= GHOST_IME_KEY_CONTROL_CHAR;
521 return;
522 }
523}
524
525- (bool)ime_did_composition
526{
527 return (ime.state_flag & GHOST_IME_COMPOSITION_EVENT) ||
528 (ime.state_flag & GHOST_IME_RESULT_EVENT);
529}
530
531/* Even if IME is enabled, when not composing, control characters
532 * (such as arrow, enter, delete) are handled by handleKeyEvent. */
533- (bool)isProcessedByIme
534{
535 return (
536 (ime.state_flag & GHOST_IME_ENABLED) &&
537 ((ime.state_flag & GHOST_IME_COMPOSING) || !(ime.state_flag & GHOST_IME_KEY_CONTROL_CHAR)));
538}
539#endif /* WITH_INPUT_IME */
540
541@end
void * GHOST_TUserDataPtr
Definition GHOST_Types.h:85
GHOST_TEventType
@ GHOST_kEventWindowUpdate
#define HANDLE_KEY_EVENT(eventType)
#define HANDLE_TABLET_EVENT(eventType)
#define HANDLE_MOUSE_EVENT(eventType)
PyObject * self
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
#define str(s)
signed int int32_t
Definition stdint.h:77