• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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