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