1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h" 6 7static NSString* const kTextAffinityDownstream = @"TextAffinity.downstream"; 8static NSString* const kTextAffinityUpstream = @"TextAffinity.upstream"; 9 10static NSString* const kTextInputAction = @"inputAction"; 11static NSString* const kTextInputType = @"inputType"; 12static NSString* const kTextInputTypeName = @"name"; 13 14static NSString* const kSelectionBaseKey = @"selectionBase"; 15static NSString* const kSelectionExtentKey = @"selectionExtent"; 16static NSString* const kSelectionAffinityKey = @"selectionAffinity"; 17static NSString* const kSelectionIsDirectionalKey = @"selectionIsDirectional"; 18static NSString* const kComposingBaseKey = @"composingBase"; 19static NSString* const kComposingExtentKey = @"composingExtent"; 20static NSString* const kTextKey = @"text"; 21 22/** 23 * These three static methods are necessary because Cocoa and Flutter have different idioms for 24 * signaling an empty range: Flutter uses {-1, -1} while Cocoa uses {NSNotFound, 0}. Also, 25 * despite the name, the "extent" fields are actually end indices, not lengths. 26 */ 27 28/** 29 * Updates a range given base and extent fields. 30 */ 31static NSRange UpdateRangeFromBaseExtent(NSNumber* base, NSNumber* extent, NSRange range) { 32 if (base == nil || extent == nil) { 33 return range; 34 } 35 if (base.intValue == -1 && extent.intValue == -1) { 36 range.location = NSNotFound; 37 range.length = 0; 38 } else { 39 range.location = [base unsignedLongValue]; 40 range.length = [extent unsignedLongValue] - range.location; 41 } 42 return range; 43} 44 45/** 46 * Returns the appropriate base field for a given range. 47 */ 48static long GetBaseForRange(NSRange range) { 49 if (range.location == NSNotFound) { 50 return -1; 51 } 52 return range.location; 53} 54 55/** 56 * Returns the appropriate extent field for a given range. 57 */ 58static long GetExtentForRange(NSRange range) { 59 if (range.location == NSNotFound) { 60 return -1; 61 } 62 return range.location + range.length; 63} 64 65@implementation FlutterTextInputModel 66 67- (instancetype)initWithClientID:(NSNumber*)clientID configuration:(NSDictionary*)config { 68 self = [super init]; 69 if (self != nil) { 70 _clientID = clientID; 71 _inputAction = config[kTextInputAction]; 72 // There's more information that can be used from this dictionary. 73 // Add more as needed. 74 NSDictionary* inputTypeInfo = config[kTextInputType]; 75 _inputType = inputTypeInfo[kTextInputTypeName]; 76 if (!_clientID || !_inputAction || !_inputType) { 77 NSLog(@"Missing arguments for %@ init.", [self class]); 78 return nil; 79 } 80 81 _text = [[NSMutableString alloc] init]; 82 _selectedRange = NSMakeRange(NSNotFound, 0); 83 _markedRange = NSMakeRange(NSNotFound, 0); 84 _textAffinity = FlutterTextAffinityUpstream; 85 } 86 return self; 87} 88 89- (NSDictionary*)state { 90 NSString* const textAffinity = (_textAffinity == FlutterTextAffinityUpstream) 91 ? kTextAffinityUpstream 92 : kTextAffinityDownstream; 93 NSDictionary* state = @{ 94 kSelectionBaseKey : @(GetBaseForRange(_selectedRange)), 95 kSelectionExtentKey : @(GetExtentForRange(_selectedRange)), 96 kSelectionAffinityKey : textAffinity, 97 kSelectionIsDirectionalKey : @NO, 98 kComposingBaseKey : @(GetBaseForRange(_markedRange)), 99 kComposingExtentKey : @(GetExtentForRange(_markedRange)), 100 kTextKey : _text 101 }; 102 return state; 103} 104 105- (void)setState:(NSDictionary*)state { 106 if (state == nil) 107 return; 108 109 _selectedRange = UpdateRangeFromBaseExtent(state[kSelectionBaseKey], state[kSelectionExtentKey], 110 _selectedRange); 111 NSString* selectionAffinity = state[kSelectionAffinityKey]; 112 if (selectionAffinity != nil) { 113 _textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream] 114 ? FlutterTextAffinityUpstream 115 : FlutterTextAffinityDownstream; 116 } 117 _markedRange = 118 UpdateRangeFromBaseExtent(state[kComposingBaseKey], state[kComposingExtentKey], _markedRange); 119 NSString* text = state[kTextKey]; 120 if (text != nil) 121 [_text setString:text]; 122} 123 124@end 125