#import <AppKit/AppKit.h>
#import <Carbon/Carbon.h>
#include "FrnView.h"
#include "automation.h"
#import "WindowInterfaces.h"
#import "WindowImpl.h"
@implementation FrnView
{
ComObjectWeakPtr<TopLevelImpl> _parent;
NSTrackingArea* _area;
FrnInputModifiers _modifierState;
NSEvent* _lastMouseDownEvent;
FrnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _currentRenderTarget;
FrnPlatformResizeReason _resizeReason;
NSRect _cursorRect;
NSMutableAttributedString* _text;
NSRange _selectedRange;
NSRange _markedRange;
NSMutableArray* _accessibilityChildren;
NSString* _keyboardInputSourceId;
bool _keyboardInputSourceComposes;
}
- (void)onClosed
{
@synchronized (self)
{
_parent = nullptr;
}
}
- (NSEvent*) lastMouseDownEvent
{
return _lastMouseDownEvent;
}
- (void) updateRenderTarget
{
if(_currentRenderTarget) {
FrnPixelSize size { MAX(_lastPixelSize.Width, 1), MAX(_lastPixelSize.Height, 1) };
[_currentRenderTarget resize:size withScale:static_cast<float>([[self window] backingScaleFactor])];
[self setNeedsDisplayInRect:[self frame]];
}
}
-(void) setRenderTarget:(NSObject<IRenderTarget>*)target
{
if([self layer])
{
[self layer].delegate = nil;
}
_currentRenderTarget = target;
auto layer = [target layer];
[self setLayer: layer];
[layer setDelegate: self];
layer.needsDisplayOnBoundsChange = YES;
[self updateRenderTarget];
}
-(void)displayLayer: (CALayer*)layer
{
[self updateLayer];
}
-(FrnView*) initWithParent: (TopLevelImpl*) parent
{
self = [super init];
[self setWantsLayer:YES];
[self setLayerContentsPlacement: NSViewLayerContentsPlacementTopLeft];
[self setCanDrawSubviewsIntoLayer: NO];
[self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
_parent = parent;
NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved |
NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag | NSTrackingInVisibleRect;
_area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:options owner:self userInfo:nullptr];
[self addTrackingArea:_area];
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
[self registerForDraggedTypes: @[@"public.data", GetFrnCustomDataType()]];
_modifierState = FrnInputModifiersNone;
_text = [[NSMutableAttributedString alloc] initWithString:@""];
_markedRange = NSMakeRange(0, 0);
_selectedRange = NSMakeRange(0, 0);
return self;
}
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)wantsUpdateLayer
{
return YES;
}
- (BOOL)isOpaque
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return true;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
return true;
}
- (BOOL)canBecomeKeyView
{
return true;
}
-(void)setFrameSize:(NSSize)newSize
{
[super setFrameSize:newSize];
auto parent = _parent.tryGet();
if (parent == nullptr)
{
return;
}
parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
{
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
parent->TopLevelEvents->Resized(FromNSSize(newSize), reason);
}
}
- (void)updateLayer
{
FrnInsidePotentialDeadlock deadlock;
auto parent = _parent.tryGet();
if (parent == nullptr)
{
return;
}
parent->TopLevelEvents->RunRenderPriorityJobs();
parent = _parent.tryGet();
if (parent == nullptr)
{
return;
}
parent->TopLevelEvents->Paint();
}
- (void)drawRect:(NSRect)dirtyRect
{
return;
}
- (FrnPoint) translateLocalPoint:(FrnPoint)pt
{
pt.Y = [self bounds].size.height - pt.Y;
return pt;
}
- (void) viewDidChangeBackingProperties
{
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
auto parent = _parent.tryGet();
if(parent != nullptr)
{
parent->TopLevelEvents->ScalingChanged([[self window] backingScaleFactor]);
}
[super viewDidChangeBackingProperties];
}
- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
{
auto parent = _parent.tryGet();
if(parent == nullptr)
{
return TRUE;
}
id<FrnWindowProtocol> parentWindow = nullptr;
if([[self window] conformsToProtocol:@protocol(FrnWindowProtocol)]){
parentWindow = (id<FrnWindowProtocol>)[self window];
}
if(parentWindow != nullptr && ![parentWindow shouldTryToHandleEvents])
{
if(trigerInputWhenDisabled)
{
auto windowImpl = _parent.tryGetWithCast<WindowImpl>();
if(windowImpl == nullptr){
return FALSE;
}
windowImpl->WindowEvents->GotInputWhenDisabled();
}
return TRUE;
}
return FALSE;
}
static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
{
*xTilt = tilt.x * 90;
*yTilt = -tilt.y * 90;
}
- (void)mouseEvent:(NSEvent *)event withType:(FrnRawMouseEventType) type
{
bool triggerInputWhenDisabled = type != Move && type != LeaveWindow;
if([self ignoreUserInput: triggerInputWhenDisabled])
{
return;
}
NSPoint eventLocation = event.type == NSEventTypeMouseEntered || event.type == NSEventTypeMouseExited
? [[self window] mouseLocationOutsideOfEventStream]
: [event locationInWindow];
auto viewLocation = [self convertPoint:NSMakePoint(0, 0) toView:nil];
auto localPoint = NSMakePoint(eventLocation.x - viewLocation.x, viewLocation.y - eventLocation.y);
auto point = ToFrnPoint(localPoint);
FrnVector delta = { 0, 0};
if(type == Wheel)
{
auto speed = 5;
if([event hasPreciseScrollingDeltas])
{
speed = 50;
}
delta.X = [event scrollingDeltaX] / speed;
delta.Y = [event scrollingDeltaY] / speed;
if(delta.X == 0 && delta.Y == 0)
{
return;
}
}
else if (type == Magnify)
{
delta.X = delta.Y = [event magnification];
}
else if (type == Rotate)
{
delta.X = delta.Y = [event rotation];
}
else if (type == Swipe)
{
delta.X = [event deltaX];
delta.Y = [event deltaY];
}
float pressure = 0.5f;
float xTilt = 0.0f;
float yTilt = 0.0f;
FrnPointerDeviceType pointerType = FrnPointerDeviceType::Mouse;
switch (event.type)
{
case NSEventTypeLeftMouseDown:
case NSEventTypeLeftMouseDragged:
case NSEventTypeRightMouseDown:
case NSEventTypeRightMouseDragged:
case NSEventTypeOtherMouseDown:
case NSEventTypeOtherMouseDragged:
switch (event.subtype)
{
case NSEventSubtypeTabletPoint:
pointerType = FrnPointerDeviceType::Pen;
pressure = event.pressure;
ConvertTilt(event.tilt, &xTilt, &yTilt);
break;
case NSEventSubtypeTabletProximity:
pointerType = FrnPointerDeviceType::Pen;
pressure = 0.0f;
break;
default:
break;
}
break;
case NSEventTypeLeftMouseUp:
case NSEventTypeRightMouseUp:
case NSEventTypeOtherMouseUp:
case NSEventTypeMouseMoved:
switch (event.subtype)
{
case NSEventSubtypeTabletPoint:
pointerType = FrnPointerDeviceType::Pen;
pressure = 0.0f;
ConvertTilt(event.tilt, &xTilt, &yTilt);
break;
case NSEventSubtypeTabletProximity:
pointerType = FrnPointerDeviceType::Pen;
pressure = 0.0f;
break;
default:
break;
}
break;
default:
break;
}
uint64_t timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]];
if(type != Move ||
(
[self window] != nil &&
(
[[self window] firstResponder] == nil
|| ![[[self window] firstResponder] isKindOfClass: [NSView class]]
)
)
)
{
auto windowBase = _parent.tryGetWithCast<WindowBaseImpl>();
if(windowBase != nullptr){
auto parent = windowBase->Parent.tryGet();
if(parent != nullptr){
auto parentWindow = parent->Window;
if(parentWindow != nullptr)
[parentWindow makeFirstResponder:parent->View];
}
} else{
[self becomeFirstResponder];
}
}
if([self hasMarkedText] &&
(type == LeftButtonDown || type == RightButtonDown || type == MiddleButtonDown ||
type == XButton1Down || type == XButton2Down) &&
[self inputContext] != nil)
{
[[self inputContext] handleEvent:event];
}
auto parent = _parent.tryGet();
if(parent != nullptr)
{
parent->TopLevelEvents->RawMouseEvent(type, pointerType, timestamp, modifiers, point, delta, pressure, xTilt, yTilt);
}
if (event.type == NSEventTypeMouseMoved)
[super mouseMoved:event];
}
- (BOOL) resignFirstResponder
{
auto window = [self window];
if (window != nullptr && window.keyWindow)
{
[self onLostFocus];
}
return YES;
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
auto oldWindow = [self window];
if (oldWindow == newWindow)
{
return;
}
if (oldWindow != nullptr)
{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:@"NSWindowDidResignKeyNotification"
object: oldWindow];
}
if (newWindow != nullptr)
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowDidResignKey:)
name:@"NSWindowDidResignKeyNotification"
object: newWindow];
}
}
- (void)windowDidResignKey:(NSNotification*)notification
{
auto window = [self window];
if (window != nullptr && notification.object == window && [window firstResponder] == self)
{
[self onLostFocus];
}
}
- (void)onLostFocus
{
auto parent = _parent.tryGet();
if (parent)
parent->TopLevelEvents->LostFocus();
}
- (void)mouseMoved:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
}
- (void)mouseDown:(NSEvent *)event
{
_lastMouseDownEvent = event;
[self mouseEvent:event withType:LeftButtonDown];
}
- (void)otherMouseDown:(NSEvent *)event
{
_lastMouseDownEvent = event;
switch(event.buttonNumber)
{
case 2:
[self mouseEvent:event withType:MiddleButtonDown];
break;
case 3:
[self mouseEvent:event withType:XButton1Down];
break;
case 4:
[self mouseEvent:event withType:XButton2Down];
break;
default:
break;
}
}
- (void)rightMouseDown:(NSEvent *)event
{
_lastMouseDownEvent = event;
[self mouseEvent:event withType:RightButtonDown];
}
- (void)mouseUp:(NSEvent *)event
{
[self mouseEvent:event withType:LeftButtonUp];
}
- (void)otherMouseUp:(NSEvent *)event
{
switch(event.buttonNumber)
{
case 2:
[self mouseEvent:event withType:MiddleButtonUp];
break;
case 3:
[self mouseEvent:event withType:XButton1Up];
break;
case 4:
[self mouseEvent:event withType:XButton2Up];
break;
default:
break;
}
}
- (void)rightMouseUp:(NSEvent *)event
{
[self mouseEvent:event withType:RightButtonUp];
}
- (void)mouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super mouseDragged:event];
}
- (void)otherMouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super otherMouseDragged:event];
}
- (void)rightMouseDragged:(NSEvent *)event
{
[self mouseEvent:event withType:Move];
[super rightMouseDragged:event];
}
- (void)scrollWheel:(NSEvent *)event
{
[self mouseEvent:event withType:Wheel];
[super scrollWheel:event];
}
- (void)magnifyWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Magnify];
[super magnifyWithEvent:event];
}
- (void)rotateWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Rotate];
[super rotateWithEvent:event];
}
- (void)swipeWithEvent:(NSEvent *)event
{
[self mouseEvent:event withType:Swipe];
[super swipeWithEvent:event];
}
- (void)mouseEntered:(NSEvent *)event
{
NSPoint location = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
if (NSPointInRect(location, self.bounds))
[self mouseEvent:event withType:Move];
[super mouseEntered:event];
}
- (void)mouseExited:(NSEvent *)event
{
[self mouseEvent:event withType:LeaveWindow];
[super mouseExited:event];
}
- (void) keyboardEvent: (NSEvent *) event withType: (FrnRawKeyEventType)type
{
auto parent = _parent.tryGet();
if([self ignoreUserInput: false] || parent == nullptr)
{
return;
}
auto scanCode = [event keyCode];
auto key = VirtualKeyFromScanCode(scanCode, [event modifierFlags]);
auto physicalKey = PhysicalKeyFromScanCode(scanCode);
auto keySymbol = KeySymbolFromScanCode(scanCode, [event modifierFlags]);
auto keySymbolUtf8 = keySymbol == nullptr ? nullptr : [keySymbol UTF8String];
auto timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto modifiers = [self getModifiers:[event modifierFlags]];
parent->TopLevelEvents->RawKeyEvent(type, timestamp, modifiers, key, physicalKey, keySymbolUtf8);
}
- (void)setModifiers:(NSEventModifierFlags)modifierFlags
{
_modifierState = [self getModifiers:modifierFlags];
}
- (void)flagsChanged:(NSEvent *)event
{
auto newModifierState = [self getModifiers:[event modifierFlags]];
bool isAltCurrentlyPressed = (_modifierState & Alt) == Alt;
bool isControlCurrentlyPressed = (_modifierState & Control) == Control;
bool isShiftCurrentlyPressed = (_modifierState & Shift) == Shift;
bool isCommandCurrentlyPressed = (_modifierState & Windows) == Windows;
bool isAltPressed = (newModifierState & Alt) == Alt;
bool isControlPressed = (newModifierState & Control) == Control;
bool isShiftPressed = (newModifierState & Shift) == Shift;
bool isCommandPressed = (newModifierState & Windows) == Windows;
if (isAltPressed && !isAltCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if (isAltCurrentlyPressed && !isAltPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if (isControlPressed && !isControlCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if (isControlCurrentlyPressed && !isControlPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if (isShiftPressed && !isShiftCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if(isShiftCurrentlyPressed && !isShiftPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
if(isCommandPressed && !isCommandCurrentlyPressed)
{
[self keyboardEvent:event withType:KeyDown];
}
else if(isCommandCurrentlyPressed && ! isCommandPressed)
{
[self keyboardEvent:event withType:KeyUp];
}
_modifierState = newModifierState;
[[self inputContext] handleEvent:event];
[super flagsChanged:event];
}
static CFStringRef const AlphanumericInputModeId = CFSTR("com.apple.inputmethod.Roman");
static bool InputSourceComposes(TISInputSourceRef source)
{
auto type = static_cast<CFStringRef>(TISGetInputSourceProperty(source, kTISPropertyInputSourceType));
if(type == nullptr || CFEqual(type, kTISTypeKeyboardLayout))
{
return false;
}
auto mode = static_cast<CFStringRef>(TISGetInputSourceProperty(source, kTISPropertyInputModeID));
return mode == nullptr || !CFEqual(mode, AlphanumericInputModeId);
}
static bool CanStartComposition(NSString* keySymbol, NSEventModifierFlags modifierFlags)
{
if(keySymbol == nullptr || [keySymbol length] == 0)
{
return false;
}
auto firstChar = [keySymbol characterAtIndex:0];
if(firstChar < 0x20 || firstChar == 0x7F)
{
return false;
}
return (modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)) == 0;
}
static bool KeyboardInputSourceComposes(NSString* sourceId)
{
NSDictionary* filter = @{ (__bridge NSString*)kTISPropertyInputSourceID: sourceId };
auto sources = TISCreateInputSourceList((__bridge CFDictionaryRef)filter, true);
if(sources == nullptr)
{
return false;
}
auto composes = CFArrayGetCount(sources) > 0 &&
InputSourceComposes((TISInputSourceRef)CFArrayGetValueAtIndex(sources, 0));
CFRelease(sources);
return composes;
}
- (bool)isComposingInputSourceSelected
{
auto inputContext = [self inputContext];
if(inputContext == nullptr)
{
return false;
}
NSString* sourceId = [inputContext selectedKeyboardInputSource];
if(sourceId == nullptr)
{
return false;
}
if(![sourceId isEqualToString:_keyboardInputSourceId])
{
_keyboardInputSourceId = sourceId;
_keyboardInputSourceComposes = KeyboardInputSourceComposes(sourceId);
}
return _keyboardInputSourceComposes;
}
- (void)keyDown:(NSEvent *)event
{
auto parent = _parent.tryGet();
if([self ignoreUserInput: false] || parent == nullptr)
{
return;
}
auto timestamp = static_cast<uint64_t>([event timestamp] * 1000);
auto scanCode = [event keyCode];
auto physicalKey = PhysicalKeyFromScanCode(scanCode);
auto keySymbol = KeySymbolFromScanCode(scanCode, [event modifierFlags]);
auto keySymbolUtf8 = keySymbol == nullptr ? nullptr : [keySymbol UTF8String];
auto modifiers = [self getModifiers:[event modifierFlags]];
auto imeProcessed = parent->InputMethod->IsActive() &&
([self hasMarkedText] ||
(CanStartComposition(keySymbol, [event modifierFlags]) &&
[self isComposingInputSourceSelected]));
auto key = imeProcessed
? FrnKeyImeProcessed
: VirtualKeyFromScanCode(scanCode, [event modifierFlags]);
auto handled = parent->TopLevelEvents->RawKeyEvent(KeyDown, timestamp, modifiers, key, physicalKey, keySymbolUtf8);
if(handled)
{
return;
}
if(parent->InputMethod->IsActive())
{
[[self inputContext] handleEvent:event];
}
else if(keySymbol != nullptr && key != FrnKeyEnter)
{
parent->TopLevelEvents->RawTextInputEvent(timestamp, keySymbolUtf8);
}
}
- (void)keyUp:(NSEvent *)event
{
[self keyboardEvent:event withType:KeyUp];
[super keyUp:event];
}
- (void) doCommandBySelector:(SEL)selector{
}
- (FrnInputModifiers)getModifiers:(NSEventModifierFlags)mod
{
unsigned int rv = 0;
if (mod & NSEventModifierFlagControl)
rv |= Control;
if (mod & NSEventModifierFlagShift)
rv |= Shift;
if (mod & NSEventModifierFlagOption)
rv |= Alt;
if (mod & NSEventModifierFlagCommand)
rv |= Windows;
NSUInteger pressedButtons = [NSEvent pressedMouseButtons];
if (pressedButtons & (1 << 0)) rv |= LeftMouseButton;
if (pressedButtons & (1 << 1)) rv |= RightMouseButton;
if (pressedButtons & (1 << 2)) rv |= MiddleMouseButton;
if (pressedButtons & (1 << 3)) rv |= XButton1MouseButton;
if (pressedButtons & (1 << 4)) rv |= XButton2MouseButton;
return (FrnInputModifiers)rv;
}
- (NSRange)clampRangeToText:(NSRange)range
{
if (range.location == NSNotFound)
return NSMakeRange(NSNotFound, 0);
NSUInteger length = _text.length;
if (range.location > length)
return NSMakeRange(length, 0);
return NSMakeRange(range.location, MIN(range.length, length - range.location));
}
- (BOOL)hasMarkedText
{
return _markedRange.location != NSNotFound && _markedRange.length > 0;
}
- (NSRange)markedRange
{
if (![self hasMarkedText])
return NSMakeRange(NSNotFound, 0);
return NSMakeRange(MIN(_markedRange.location, _text.length), _markedRange.length);
}
- (NSRange)selectedRange
{
return [self clampRangeToText:_selectedRange];
}
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
NSString* markedText;
if([string isKindOfClass:[NSAttributedString class]])
{
markedText = [string string];
}
else
{
markedText = (NSString*) string;
}
if (markedText == nil)
{
markedText = @"";
}
auto parent = _parent.tryGet();
auto finalReplacementRange = [self clampRangeToText:replacementRange];
if (finalReplacementRange.location != NSNotFound && parent != nullptr && parent->InputMethod->IsActive())
{
parent->InputMethod->Client->SelectInSurroundingText((int)finalReplacementRange.location, (int)(finalReplacementRange.location + finalReplacementRange.length));
uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000);
parent->TopLevelEvents->RawKeyEvent(KeyDown, timestamp, FrnInputModifiersNone, FrnKeyBack, FrnPhysicalKeyNone, "\b");
parent->TopLevelEvents->RawKeyEvent(KeyUp, timestamp, FrnInputModifiersNone, FrnKeyBack, FrnPhysicalKeyNone, "\b");
}
auto markedLocation = [self selectedRange].location;
_markedRange = NSMakeRange(markedLocation == NSNotFound ? 0 : markedLocation, [markedText length]);
if (parent != nullptr && parent->InputMethod->IsActive())
{
const char* utf8MarkedText = [markedText UTF8String];
parent->InputMethod->Client->SetPreeditText((char*)(utf8MarkedText != nullptr ? utf8MarkedText : ""));
}
}
- (void)unmarkText
{
auto parent = _parent.tryGet();
if(parent != nullptr && parent->InputMethod->IsActive()){
parent->InputMethod->Client->SetPreeditText(nullptr);
}
auto selectionLocation = [self selectedRange].location;
_markedRange = NSMakeRange(selectionLocation == NSNotFound ? 0 : selectionLocation, 0);
if([self inputContext]) {
[[self inputContext] discardMarkedText];
}
}
- (NSArray<NSString *> *)validAttributesForMarkedText
{
return [NSArray new];
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
NSRange docRange = NSMakeRange(0, _text.length);
NSRange finalRange = NSIntersectionRange(range, docRange);
if (finalRange.length == 0)
{
if (actualRange) {
*actualRange = NSMakeRange(NSNotFound, 0);
}
return nil;
}
if (actualRange) {
*actualRange = finalRange;
}
return [_text attributedSubstringFromRange:finalRange];
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
auto parent = _parent.tryGet();
if(parent == nullptr){
return;
}
NSString* text;
if([string isKindOfClass:[NSAttributedString class]])
{
text = [string string];
}
else
{
text = (NSString*) string;
}
if (text == nil)
{
text = @"";
}
auto finalReplacementRange = [self clampRangeToText:replacementRange];
if (finalReplacementRange.location != NSNotFound &&
![self hasMarkedText] &&
parent->InputMethod->IsActive())
{
parent->InputMethod->Client->SelectInSurroundingText((int)finalReplacementRange.location, (int)(finalReplacementRange.location + finalReplacementRange.length));
}
[self unmarkText];
uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000);
const char* utf8Text = [text UTF8String];
parent->TopLevelEvents->RawTextInputEvent(timestamp, utf8Text != nullptr ? utf8Text : "");
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point
{
return NSNotFound;
}
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
if (actualRange) {
*actualRange = [self clampRangeToText:range];
}
auto parent = _parent.tryGet();
if(parent == nullptr || !parent->InputMethod->IsActive()){
return NSZeroRect;
}
return _cursorRect;
}
- (NSDragOperation)triggerFrnDragEvent: (FrnDragEventType) type info: (id <NSDraggingInfo>)info
{
NSPoint eventLocation = [info draggingLocation];
auto viewLocation = [self convertPoint:NSMakePoint(0, 0) toView:nil];
auto localPoint = NSMakePoint(eventLocation.x - viewLocation.x, viewLocation.y - eventLocation.y);
auto point = ToFrnPoint(localPoint);
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
auto effects = ConvertDragDropEffects(nsop);
auto parent = _parent.tryGet();
if (!parent)
return NSDragOperationNone;
int reffects = (int)parent->TopLevelEvents
->DragEvent(type, point, modifiers, effects,
CreateClipboard([info draggingPasteboard]),
GetFrnDataObjectHandleFromDraggingInfo(info));
NSDragOperation ret = static_cast<NSDragOperation>(0);
reffects = (int)effects & reffects;
if((reffects & (int)FrnDragDropEffects::Copy) != 0)
ret = NSDragOperationCopy;
else if((reffects & (int)FrnDragDropEffects::Move) != 0)
ret = NSDragOperationMove;
else if((reffects & (int)FrnDragDropEffects::Link) != 0)
ret = NSDragOperationLink;
if(ret == 0)
ret = NSDragOperationNone;
return ret;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
return [self triggerFrnDragEvent: FrnDragEventType::Enter info:sender];
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
return [self triggerFrnDragEvent: FrnDragEventType::Over info:sender];
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
[self triggerFrnDragEvent: FrnDragEventType::Leave info:sender];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerFrnDragEvent: FrnDragEventType::Over info:sender] != NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerFrnDragEvent: FrnDragEventType::Drop info:sender] != NSDragOperationNone;
}
- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
{
}
- (FrnPlatformResizeReason)getResizeReason
{
return _resizeReason;
}
- (void)setResizeReason:(FrnPlatformResizeReason)reason
{
_resizeReason = reason;
}
- (NSArray *)accessibilityChildren
{
if (_accessibilityChildren == nil)
[self recalculateAccessibiltyChildren];
return _accessibilityChildren;
}
- (id _Nullable) accessibilityHitTest:(NSPoint)point
{
if (![[self window] isKindOfClass:[FrnWindow class]])
return self;
auto window = (FrnWindow*)[self window];
auto peer = [window automationPeer];
if (peer == nullptr || !peer->IsRootProvider())
return nil;
auto clientPoint = [window convertPointFromScreen:point];
auto localPoint = [self translateLocalPoint:ToFrnPoint(clientPoint)];
auto hit = peer->RootProvider_GetPeerFromPoint(localPoint);
return [FrnAccessibilityElement acquire:hit];
}
- (void)raiseAccessibilityChildrenChanged
{
auto changed = _accessibilityChildren ? [NSMutableSet setWithArray:_accessibilityChildren] : [NSMutableSet set];
[self recalculateAccessibiltyChildren];
if (_accessibilityChildren)
[changed addObjectsFromArray:_accessibilityChildren];
NSAccessibilityPostNotificationWithUserInfo(
self,
NSAccessibilityLayoutChangedNotification,
@{ NSAccessibilityUIElementsKey: [changed allObjects]});
}
- (void)recalculateAccessibiltyChildren
{
_accessibilityChildren = [[NSMutableArray alloc] init];
if (![[self window] isKindOfClass:[FrnWindow class]])
{
return;
}
auto window = (FrnWindow*)[self window];
auto peer = [window automationPeer];
if (peer == nullptr)
{
return;
}
auto childPeers = peer->GetChildren();
auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0;
if (childCount > 0)
{
for (int i = 0; i < childCount; ++i)
{
IFrnAutomationPeer* child;
if (childPeers->Get(i, &child) == S_OK)
{
id element = [FrnAccessibilityElement acquire:child];
[_accessibilityChildren addObject:element];
}
}
}
}
- (void) setText:(NSString *)text{
[[_text mutableString] setString:text != nil ? text : @""];
_selectedRange = [self clampRangeToText:_selectedRange];
if (_markedRange.location != NSNotFound)
{
_markedRange = NSMakeRange(MIN(_markedRange.location, _text.length), _markedRange.length);
}
}
- (void) setSelection:(int)start :(int)end{
if (end < start)
{
auto temp = start;
start = end;
end = temp;
}
auto length = (int)_text.length;
start = MAX(0, MIN(start, length));
end = MAX(start, MIN(end, length));
_selectedRange = NSMakeRange((NSUInteger)start, (NSUInteger)(end - start));
}
- (void) resetInputMethod{
auto parent = _parent.tryGet();
if(parent != nullptr && parent->InputMethod->IsActive()){
parent->InputMethod->Client->SetPreeditText(nullptr);
}
_markedRange = NSMakeRange(_selectedRange.location, 0);
if([self inputContext]) {
[[self inputContext] discardMarkedText];
}
}
- (void) setCursorRect:(FrnRect)rect{
NSRect cursorRect = ToNSRect(rect);
NSRect windowRectOnScreen = [[self window] convertRectToScreen:self.frame];
windowRectOnScreen.size = cursorRect.size;
windowRectOnScreen.origin = NSMakePoint(windowRectOnScreen.origin.x + cursorRect.origin.x, windowRectOnScreen.origin.y + self.frame.size.height - cursorRect.origin.y - cursorRect.size.height);
_cursorRect = windowRectOnScreen;
if([self inputContext]) {
[[self inputContext] invalidateCharacterCoordinates];
}
}
@end