forked from mikecsh/mbtablegrid
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathMBTableGridTextFinderClient.m
More file actions
228 lines (186 loc) · 9.24 KB
/
MBTableGridTextFinderClient.m
File metadata and controls
228 lines (186 loc) · 9.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//
// MBTableGridTextFinderClient.m
// MBTableGrid
//
// Created by Evan Miller on 1/17/20.
//
#import "MBTableGridTextFinderClient.h"
#import "MBTableGridVirtualString.h"
#import "MBTableGrid.h"
#import "MBTableGridContentView.h"
//enabling support for boxed NSRange
typedef struct __attribute__((objc_boxable)) _NSRange NSRange;
@interface MBTableGrid ()
- (NSCell *)_cellForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex;
- (id)_objectValueForColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex;
- (void)_setObjectValue:(id)value forColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex;
- (void)_setObjectValue:(id)value forColumns:(NSIndexSet *)columnIndexes rows:(NSIndexSet *)rowIndexes;
- (BOOL)_canEditCellAtColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex;
- (void)scrollToArea:(NSRect)area animate:(BOOL)animate;
@end
@implementation MBTableGridTextFinderClient
// Pros and cons of string vs stringAtIndex:effectiveRange:endsWithSearchBoundary
// - the returned `string` can be subclassed - i.e. do a search without creating any substrings
// - `string` can be cancelled inside of rangeOfString:options: (read a value and return NSNotFound)
// - `stringAtIndex:` crashes with 10s of thousands of substrings - AppKit bug
// - BUT `string` searches are on the main thread, whereas stringAtIndex: gets kicked off to worker threads
// - So `string` looks faster and more stable overall - at the cost of pinning the UI on large tables. Sigh.
#define _row(cellIndex, rows, cols) ((cellIndex) % (rows) + (cols-cols))
#define _col(cellIndex, rows, cols) ((cellIndex) / (rows) + (cols-cols))
#define _cell(rowIndex, columnIndex, rowCount, columCount) ((columnIndex) * (rowCount) + (rowIndex) + (columnCount-columnCount))
- (instancetype)initWithTableGrid:(MBTableGrid *)tableGrid {
if (self = [super init]) {
_tableGrid = tableGrid;
_pending_replacements = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)string {
return [[MBTableGridVirtualString alloc] initWithTableGrid:_tableGrid];
}
- (NSRange)firstSelectedRange {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
NSUInteger rowIndex = 0, columnIndex = 0;
if (_tableGrid.selectedRowIndexes)
rowIndex = _tableGrid.selectedRowIndexes.firstIndex;
if (_tableGrid.selectedColumnIndexes)
columnIndex = _tableGrid.selectedColumnIndexes.firstIndex;
NSUInteger cellIndex = _cell(rowIndex, columnIndex, rowCount, columnCount);
return NSMakeRange(cellIndex, 1);
}
- (NSArray<NSValue *> *)selectedRanges {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
if (rowCount == 0 || columnCount == 0)
return @[];
if (!_tableGrid.selectedRowIndexes && !_tableGrid.selectedColumnIndexes)
return @[];
NSMutableArray<NSValue *> *ranges = [NSMutableArray array];
NSUInteger minRow = 0, maxRow = rowCount-1;
NSUInteger minCol = 0, maxCol = columnCount-1;
if (_tableGrid.selectedColumnIndexes) {
minCol = _tableGrid.selectedColumnIndexes.firstIndex;
maxCol = _tableGrid.selectedColumnIndexes.lastIndex;
}
if (_tableGrid.selectedRowIndexes) {
minRow = _tableGrid.selectedRowIndexes.firstIndex;
maxRow = _tableGrid.selectedRowIndexes.lastIndex;
}
for (NSUInteger j=minCol; j<=maxCol; j++) {
NSUInteger cellIndex = _cell(minRow, j, rowCount, columnCount);
NSRange range = NSMakeRange(cellIndex, (maxRow - minRow + 1));
[ranges addObject:[NSValue valueWithRange:range]];
}
return ranges;
}
- (void)setSelectedRanges:(NSArray<NSValue *> *)selectedRanges {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
if (selectedRanges.count && columnCount && rowCount) {
NSUInteger cellIndex = selectedRanges.firstObject.rangeValue.location;
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger filteredIndex = _col(cellIndex, rowCount, columnCount);
_tableGrid.selectedRowIndexes = [NSIndexSet indexSetWithIndex:rowIndex];
_tableGrid.selectedColumnIndexes = [NSIndexSet indexSetWithIndex:filteredIndex];
}
}
- (void)scrollRangeToVisible:(NSRange)range {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
NSUInteger cellIndex = range.location;
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger filteredIndex = _col(cellIndex, rowCount, columnCount);
[_tableGrid scrollToArea:[_tableGrid.contentView frameOfCellAtColumn:filteredIndex row:rowIndex] animate:NO];
}
- (NSView *)contentViewAtIndex:(NSUInteger)index effectiveCharacterRange:(NSRangePointer)outRange {
// One big fat view
outRange->location = 0;
outRange->length = _tableGrid.numberOfColumns * _tableGrid.numberOfRows;
return _tableGrid.contentView;
}
- (NSArray<NSValue *> *)rectsForCharacterRange:(NSRange)range {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
NSUInteger cellIndex = range.location;
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger filteredIndex = _col(cellIndex, rowCount, columnCount);
return @[ @([_tableGrid.contentView frameOfCellAtColumn:filteredIndex row:rowIndex] ) ];
}
- (NSArray<NSValue *> *)visibleCharacterRanges {
NSUInteger rowCount = _tableGrid.numberOfRows;
NSRect visibleRect = _tableGrid.contentView.visibleRect;
NSPoint topLeft = visibleRect.origin;
NSPoint bottomRight = NSMakePoint(CGRectGetMaxX(visibleRect), CGRectGetMaxY(visibleRect));
NSInteger minColumn = [_tableGrid.contentView columnAtPoint:topLeft];
NSInteger maxColumn = [_tableGrid.contentView columnAtPoint:bottomRight];
if (maxColumn == NSNotFound) {
maxColumn = _tableGrid.numberOfColumns-1;
}
NSInteger minRow = [_tableGrid.contentView rowAtPoint:topLeft];
NSInteger maxRow = [_tableGrid.contentView rowAtPoint:bottomRight];
if (maxRow == NSNotFound) {
maxRow = _tableGrid.numberOfRows-1;
}
NSMutableArray<NSValue *> *arrays_of_ranges = [NSMutableArray array];
for (NSInteger j=minColumn; j<=maxColumn; j++) {
NSInteger firstIndex = (j*rowCount + minRow);
NSInteger lastIndex = (j*rowCount + maxRow);
NSRange range = NSMakeRange(firstIndex, (lastIndex - firstIndex + 1));
[arrays_of_ranges addObject:@(range)];
}
return arrays_of_ranges;
}
- (void)drawCharactersInRange:(NSRange)range forContentView:(NSView *)view {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
NSUInteger cellIndex = range.location;
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger filteredIndex = _col(cellIndex, rowCount, columnCount);
NSCell* cell = [_tableGrid _cellForColumn:filteredIndex row:rowIndex];
NSRect cellFrame = [_tableGrid.contentView frameOfCellAtColumn:filteredIndex row:rowIndex];
[cell drawInteriorWithFrame:cellFrame inView:view];
}
- (BOOL)shouldReplaceCharactersInRanges:(NSArray<NSValue *> *)ranges withStrings:(NSArray<NSString *> *)strings {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
for (NSValue *rangeValue in ranges) {
NSRange range = rangeValue.rangeValue;
NSUInteger cellIndex = range.location;
if (range.location == NSNotFound)
continue;
while (cellIndex < range.location + range.length) {
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger filteredIndex = _col(cellIndex, rowCount, columnCount);
if (![_tableGrid _canEditCellAtColumn:filteredIndex row:rowIndex])
return NO;
cellIndex++;
}
}
return YES;
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)string {
NSUInteger columnCount = _tableGrid.numberOfColumns;
NSUInteger rowCount = _tableGrid.numberOfRows;
NSUInteger cellIndex = range.location;
NSUInteger rowIndex = _row(cellIndex, rowCount, columnCount);
NSUInteger columnIndex = _col(cellIndex, rowCount, columnCount);
if (!_pending_replacements[@(columnIndex)]) {
_pending_replacements[@(columnIndex)] = @{ @"string": string, @"indexes": [NSMutableIndexSet indexSet] };
}
[(NSMutableIndexSet *)_pending_replacements[@(columnIndex)][@"indexes"] addIndex:rowIndex];
}
- (BOOL)isEditable {
return ([_tableGrid.dataSource respondsToSelector:@selector(tableGrid:setObjectValue:forColumn:row:)] ||
[_tableGrid.dataSource respondsToSelector:@selector(tableGrid:setObjectValue:forColumns:rows:)]);
}
- (void)didReplaceCharacters {
[_pending_replacements enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSDictionary<NSString *, id> *replacement, BOOL *stop) {
[_tableGrid _setObjectValue:replacement[@"string"]
forColumns:[NSIndexSet indexSetWithIndex:key.integerValue]
rows:replacement[@"indexes"]];
}];
[_pending_replacements removeAllObjects];
[_tableGrid reloadData];
}
@end