-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMMTextStorage.m
727 lines (604 loc) · 24.3 KB
/
MMTextStorage.m
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
/* vi:set ts=8 sts=4 sw=4 ft=objc:
*
* VIM - Vi IMproved by Bram Moolenaar
* MacVim GUI port by Bjorn Winckler
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
#import "MMTextStorage.h"
#import "MacVim.h"
// TODO: support DRAW_TRANSP flag
#define DRAW_TRANSP 0x01 /* draw with transparant bg */
#define DRAW_BOLD 0x02 /* draw bold text */
#define DRAW_UNDERL 0x04 /* draw underline text */
#define DRAW_UNDERC 0x08 /* draw undercurl text */
#define DRAW_ITALIC 0x10 /* draw italic text */
@interface MMTextStorage (Private)
- (void)lazyResize:(BOOL)force;
@end
@implementation MMTextStorage
- (id)init
{
if ((self = [super init])) {
attribString = [[NSMutableAttributedString alloc] initWithString:@""];
// NOTE! It does not matter which font is set here, Vim will set its
// own font on startup anyway. Just set some bogus values.
font = [[NSFont userFixedPitchFontOfSize:0] retain];
boldFont = [font retain];
italicFont = [font retain];
boldItalicFont = [font retain];
cellSize.height = [font pointSize];
cellSize.width = [font defaultLineHeightForFont];
}
return self;
}
- (void)dealloc
{
//NSLog(@"%@ %s", [self className], _cmd);
[emptyRowString release];
[boldItalicFont release];
[italicFont release];
[boldFont release];
[font release];
[defaultBackgroundColor release];
[defaultForegroundColor release];
[attribString release];
[super dealloc];
}
- (NSString *)string
{
//NSLog(@"%s : attribString=%@", _cmd, attribString);
return [attribString string];
}
- (NSDictionary *)attributesAtIndex:(unsigned)index
effectiveRange:(NSRangePointer)range
{
//NSLog(@"%s", _cmd);
if (index>=[attribString length]) {
//NSLog(@"%sWARNING: index (%d) out of bounds", _cmd, index);
if (range) {
*range = NSMakeRange(NSNotFound, 0);
}
return [NSDictionary dictionary];
}
return [attribString attributesAtIndex:index effectiveRange:range];
}
- (void)replaceCharactersInRange:(NSRange)range
withString:(NSString *)string
{
//NSLog(@"replaceCharactersInRange:(%d,%d) withString:%@", range.location,
// range.length, string);
NSLog(@"WARNING: calling %s on MMTextStorage is unsupported", _cmd);
//[attribString replaceCharactersInRange:range withString:string];
}
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range
{
// NOTE! This method must be implemented since the text system calls it
// constantly to 'fix attributes', apply font substitution, etc.
#if 0
[attribString setAttributes:attributes range:range];
#else
// HACK! If the font attribute is being modified, then ensure that the new
// font has a fixed advancement which is either the same as the current
// font or twice that, depending on whether it is a 'wide' character that
// is being fixed or not. This code really only works if 'range' has
// length 1 or 2.
NSFont *newFont = [attributes objectForKey:NSFontAttributeName];
if (newFont) {
float adv = cellSize.width;
if ([attribString length] > range.location+1) {
// If the first char is followed by zero-width space, then it is a
// 'wide' character, so double the advancement.
NSString *string = [attribString string];
if ([string characterAtIndex:range.location+1] == 0x200b)
adv += adv;
}
// Create a new font which has the 'fixed advance attribute' set.
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:adv], NSFontFixedAdvanceAttribute, nil];
NSFontDescriptor *desc = [newFont fontDescriptor];
desc = [desc fontDescriptorByAddingAttributes:dict];
newFont = [NSFont fontWithDescriptor:desc size:[newFont pointSize]];
// Now modify the 'attributes' dictionary to hold the new font.
NSMutableDictionary *newAttr = [NSMutableDictionary
dictionaryWithDictionary:attributes];
[newAttr setObject:newFont forKey:NSFontAttributeName];
[attribString setAttributes:newAttr range:range];
} else {
[attribString setAttributes:attributes range:range];
}
#endif
}
- (int)maxRows
{
return maxRows;
}
- (int)maxColumns
{
return maxColumns;
}
- (int)actualRows
{
return actualRows;
}
- (int)actualColumns
{
return actualColumns;
}
- (float)linespace
{
return linespace;
}
- (void)setLinespace:(float)newLinespace
{
NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
linespace = newLinespace;
// NOTE: The linespace is added to the cell height in order for a multiline
// selection not to have white (background color) gaps between lines. Also
// this simplifies the code a lot because there is no need to check the
// linespace when calculating the size of the text view etc. When the
// linespace is non-zero the baseline will be adjusted as well; check
// MMTypesetter.
cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
: [font defaultLineHeightForFont]);
}
- (void)getMaxRows:(int*)rows columns:(int*)cols
{
if (rows) *rows = maxRows;
if (cols) *cols = maxColumns;
}
- (void)setMaxRows:(int)rows columns:(int)cols
{
// NOTE: Just remember the new values, the actual resizing is done lazily.
maxRows = rows;
maxColumns = cols;
}
- (void)replaceString:(NSString *)string atRow:(int)row column:(int)col
withFlags:(int)flags foregroundColor:(NSColor *)fg
backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
{
//NSLog(@"replaceString:atRow:%d column:%d withFlags:%d "
// "foreground:%@ background:%@ special:%@",
// row, col, flags, fg, bg, sp);
[self lazyResize:NO];
// TODO: support DRAW_TRANSP
if (flags & DRAW_TRANSP)
return;
if (row < 0 || row >= maxRows || col < 0 || col >= maxColumns
|| col+[string length] > maxColumns) {
//NSLog(@"[%s] WARNING : out of range, row=%d (%d) col=%d (%d) "
// "length=%d (%d)", _cmd, row, maxRows, col, maxColumns,
// [string length], [attribString length]);
return;
}
// NOTE: If 'string' was initialized with bad data it might be nil; this
// may be due to 'enc' being set to an unsupported value, so don't print an
// error message or stdout will most likely get flooded.
if (!string) return;
if (!(fg && bg && sp)) {
NSLog(@"[%s] WARNING: background, foreground or special color not "
"specified", _cmd);
return;
}
NSRange range = NSMakeRange(col+row*(maxColumns+1), [string length]);
[attribString replaceCharactersInRange:range withString:string];
NSFont *theFont = font;
if (flags & DRAW_BOLD)
theFont = flags & DRAW_ITALIC ? boldItalicFont : boldFont;
else if (flags & DRAW_ITALIC)
theFont = italicFont;
NSDictionary *attributes;
if (flags & DRAW_UNDERC) {
// move the undercurl down a bit so it is visible
attributes = [NSDictionary dictionaryWithObjectsAndKeys:
theFont, NSFontAttributeName,
bg, NSBackgroundColorAttributeName,
fg, NSForegroundColorAttributeName,
sp, NSUnderlineColorAttributeName,
[NSNumber numberWithFloat:2],NSBaselineOffsetAttributeName,
nil];
} else {
attributes = [NSDictionary dictionaryWithObjectsAndKeys:
theFont, NSFontAttributeName,
bg, NSBackgroundColorAttributeName,
fg, NSForegroundColorAttributeName,
sp, NSUnderlineColorAttributeName,
nil];
}
[attribString setAttributes:attributes range:range];
if (flags & DRAW_UNDERL) {
NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleSingle
| NSUnderlinePatternSolid)]; // | NSUnderlineByWordMask
[attribString addAttribute:NSUnderlineStyleAttributeName
value:value range:range];
}
// TODO: figure out how do draw proper undercurls
if (flags & DRAW_UNDERC) {
NSNumber *value = [NSNumber numberWithInt:(NSUnderlineStyleThick
| NSUnderlinePatternDot)]; // | NSUnderlineByWordMask
[attribString addAttribute:NSUnderlineStyleAttributeName
value:value range:range];
}
[self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
range:range changeInLength:0];
}
/*
* Delete 'count' lines from 'row' and insert 'count' empty lines at the bottom
* of the scroll region.
*/
- (void)deleteLinesFromRow:(int)row lineCount:(int)count
scrollBottom:(int)bottom left:(int)left right:(int)right
color:(NSColor *)color
{
//NSLog(@"deleteLinesFromRow:%d lineCount:%d color:%@", row, count, color);
[self lazyResize:NO];
if (row < 0 || row+count > maxRows) {
//NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
// maxRows, count);
return;
}
int total = 1 + bottom - row;
int move = total - count;
int width = right - left + 1;
NSRange destRange = { row*(maxColumns+1) + left, width };
NSRange srcRange = { (row+count)*(maxColumns+1) + left, width };
int i;
if (width != maxColumns) { // if this is the case, then left must be 0
for (i = 0; i < move; ++i) {
NSAttributedString *srcString = [attribString
attributedSubstringFromRange:srcRange];
[attribString replaceCharactersInRange:destRange
withAttributedString:srcString];
[self edited:(NSTextStorageEditedCharacters
| NSTextStorageEditedAttributes)
range:destRange changeInLength:0];
destRange.location += maxColumns+1;
srcRange.location += maxColumns+1;
}
NSRange emptyRange = {0,width};
NSAttributedString *emptyString =
[emptyRowString attributedSubstringFromRange: emptyRange];
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSBackgroundColorAttributeName, nil];
for (i = 0; i < count; ++i) {
[attribString replaceCharactersInRange:destRange
withAttributedString:emptyString];
[attribString setAttributes:attribs range:destRange];
[self edited:(NSTextStorageEditedAttributes
| NSTextStorageEditedCharacters) range:destRange
changeInLength:0];
destRange.location += maxColumns+1;
}
} else {
NSRange delRange = {row*(maxColumns+1), count*(maxColumns+1)};
[attribString deleteCharactersInRange: delRange];
destRange.location += move*(maxColumns+1);
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSBackgroundColorAttributeName, nil];
destRange.length = maxColumns;
for (i = 0; i < count; ++i) {
[attribString insertAttributedString:emptyRowString
atIndex:destRange.location];
[attribString setAttributes:attribs range:destRange];
destRange.location += maxColumns+1;
}
NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
[self edited:(NSTextStorageEditedAttributes
| NSTextStorageEditedCharacters) range:editedRange
changeInLength:0];
}
}
/*
* Insert 'count' empty lines at 'row' and delete 'count' lines from the bottom
* of the scroll region.
*/
- (void)insertLinesAtRow:(int)row lineCount:(int)count
scrollBottom:(int)bottom left:(int)left right:(int)right
color:(NSColor *)color
{
//NSLog(@"insertLinesAtRow:%d lineCount:%d color:%@", row, count, color);
[self lazyResize:NO];
if (row < 0 || row+count > maxRows) {
//NSLog(@"[%s] WARNING : out of range, row=%d (%d) count=%d", _cmd, row,
// maxRows, count);
return;
}
int total = 1 + bottom - row;
int move = total - count;
int width = right - left + 1;
NSRange destRange = { bottom*(maxColumns+1) + left, width };
NSRange srcRange = { (row+move-1)*(maxColumns+1) + left, width };
int i;
if (width != maxColumns) { // if this is the case, then left must be 0
for (i = 0; i < move; ++i) {
NSAttributedString *srcString = [attribString
attributedSubstringFromRange:srcRange];
[attribString replaceCharactersInRange:destRange
withAttributedString:srcString];
[self edited:(NSTextStorageEditedCharacters
| NSTextStorageEditedAttributes)
range:destRange changeInLength:0];
destRange.location -= maxColumns+1;
srcRange.location -= maxColumns+1;
}
NSRange emptyRange = {0,width};
NSAttributedString *emptyString =
[emptyRowString attributedSubstringFromRange:emptyRange];
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSBackgroundColorAttributeName, nil];
for (i = 0; i < count; ++i) {
[attribString replaceCharactersInRange:destRange
withAttributedString:emptyString];
[attribString setAttributes:attribs range:destRange];
[self edited:(NSTextStorageEditedAttributes
| NSTextStorageEditedCharacters) range:destRange
changeInLength:0];
destRange.location -= maxColumns+1;
}
} else {
NSRange delRange = {(row+move)*(maxColumns+1),count*(maxColumns+1)};
[attribString deleteCharactersInRange: delRange];
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSBackgroundColorAttributeName, nil];
destRange.location = row*(maxColumns+1);
for (i = 0; i < count; ++i) {
[attribString insertAttributedString:emptyRowString
atIndex:destRange.location];
[attribString setAttributes:attribs range:destRange];
}
NSRange editedRange = {row*(maxColumns+1),total*(maxColumns+1)};
[self edited:(NSTextStorageEditedAttributes
| NSTextStorageEditedCharacters) range:editedRange
changeInLength:0];
}
}
- (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
column:(int)col2 color:(NSColor *)color
{
//NSLog(@"clearBlockFromRow:%d column:%d toRow:%d column:%d color:%@",
// row1, col1, row2, col2, color);
[self lazyResize:NO];
if (row1 < 0 || row2 >= maxRows || col1 < 0 || col2 > maxColumns) {
//NSLog(@"[%s] WARNING : out of range, row1=%d row2=%d (%d) col1=%d "
// "col2=%d (%d)", _cmd, row1, row2, maxRows, col1, col2,
// maxColumns);
return;
}
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
color, NSBackgroundColorAttributeName, nil];
NSRange range = { row1*(maxColumns+1) + col1, col2-col1+1 };
NSRange emptyRange = {0,col2-col1+1};
NSAttributedString *emptyString =
[emptyRowString attributedSubstringFromRange:emptyRange];
int r;
for (r=row1; r<=row2; ++r) {
[attribString replaceCharactersInRange:range
withAttributedString:emptyString];
[attribString setAttributes:attribs range:range];
[self edited:(NSTextStorageEditedAttributes
| NSTextStorageEditedCharacters) range:range
changeInLength:0];
range.location += maxColumns+1;
}
}
- (void)clearAll
{
//NSLog(@"%s%@", _cmd, color);
[self lazyResize:YES];
}
- (void)setDefaultColorsBackground:(NSColor *)bgColor
foreground:(NSColor *)fgColor
{
//NSLog(@"setDefaultColorsBackground:%@ foreground:%@", bgColor, fgColor);
if (defaultBackgroundColor != bgColor) {
[defaultBackgroundColor release];
defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
}
// NOTE: The default foreground color isn't actually used for anything, but
// other class instances might want to be able to access it so it is stored
// here.
if (defaultForegroundColor != fgColor) {
[defaultForegroundColor release];
defaultForegroundColor = fgColor ? [fgColor retain] : nil;
}
}
- (void)setFont:(NSFont*)newFont
{
if (newFont && font != newFont) {
[font release];
// NOTE! When setting a new font we make sure that the advancement of
// each glyph is fixed.
float em = [newFont widthOfString:@"m"];
float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
floatForKey:MMCellWidthMultiplierKey];
// NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
// only render at integer sizes. Hence, we restrict the cell width to
// an integer here, otherwise the window width and the actual text
// width will not match.
cellSize.width = ceilf(em * cellWidthMultiplier);
float pointSize = [newFont pointSize];
NSDictionary *dict = [NSDictionary
dictionaryWithObject:[NSNumber numberWithFloat:cellSize.width]
forKey:NSFontFixedAdvanceAttribute];
NSFontDescriptor *desc = [newFont fontDescriptor];
desc = [desc fontDescriptorByAddingAttributes:dict];
font = [NSFont fontWithDescriptor:desc size:pointSize];
[font retain];
NSLayoutManager *lm = [[self layoutManagers] objectAtIndex:0];
cellSize.height = linespace + (lm ? [lm defaultLineHeightForFont:font]
: [font defaultLineHeightForFont]);
// NOTE: The font manager does not care about the 'font fixed advance'
// attribute, so after converting the font we have to add this
// attribute again.
boldFont = [[NSFontManager sharedFontManager]
convertFont:font toHaveTrait:NSBoldFontMask];
desc = [boldFont fontDescriptor];
desc = [desc fontDescriptorByAddingAttributes:dict];
boldFont = [NSFont fontWithDescriptor:desc size:pointSize];
[boldFont retain];
italicFont = [[NSFontManager sharedFontManager]
convertFont:font toHaveTrait:NSItalicFontMask];
desc = [italicFont fontDescriptor];
desc = [desc fontDescriptorByAddingAttributes:dict];
italicFont = [NSFont fontWithDescriptor:desc size:pointSize];
[italicFont retain];
boldItalicFont = [[NSFontManager sharedFontManager]
convertFont:italicFont toHaveTrait:NSBoldFontMask];
desc = [boldItalicFont fontDescriptor];
desc = [desc fontDescriptorByAddingAttributes:dict];
boldItalicFont = [NSFont fontWithDescriptor:desc size:pointSize];
[boldItalicFont retain];
}
}
- (NSFont*)font
{
return font;
}
- (NSColor *)defaultBackgroundColor
{
return defaultBackgroundColor;
}
- (NSColor *)defaultForegroundColor
{
return defaultForegroundColor;
}
- (NSSize)size
{
return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
}
- (NSSize)cellSize
{
return cellSize;
}
- (NSRect)rectForRowsInRange:(NSRange)range
{
NSRect rect = { 0, 0, 0, 0 };
unsigned start = range.location > maxRows ? maxRows : range.location;
unsigned length = range.length;
if (start+length > maxRows)
length = maxRows - start;
rect.origin.y = cellSize.height * start;
rect.size.height = cellSize.height * length;
return rect;
}
- (NSRect)rectForColumnsInRange:(NSRange)range
{
NSRect rect = { 0, 0, 0, 0 };
unsigned start = range.location > maxColumns ? maxColumns : range.location;
unsigned length = range.length;
if (start+length > maxColumns)
length = maxColumns - start;
rect.origin.x = cellSize.width * start;
rect.size.width = cellSize.width * length;
return rect;
}
- (unsigned)characterIndexForRow:(int)row column:(int)col
{
// Ensure the offset returned is valid.
// This code also works if maxRows and/or maxColumns is 0.
if (row >= maxRows) row = maxRows-1;
if (row < 0) row = 0;
if (col >= maxColumns) col = maxColumns-1;
if (col < 0) col = 0;
return (unsigned)(col + row*(maxColumns+1));
}
- (BOOL)resizeToFitSize:(NSSize)size
{
int rows = maxRows, cols = maxColumns;
[self fitToSize:size rows:&rows columns:&cols];
if (rows != maxRows || cols != maxColumns) {
[self setMaxRows:rows columns:cols];
return YES;
}
// Return NO only if dimensions did not change.
return NO;
}
- (NSSize)fitToSize:(NSSize)size
{
return [self fitToSize:size rows:NULL columns:NULL];
}
- (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
{
NSSize curSize = [self size];
NSSize fitSize = curSize;
int fitRows = maxRows;
int fitCols = maxColumns;
if (size.height < curSize.height) {
// Remove lines until the height of the text storage fits inside
// 'size'. However, always make sure there are at least 3 lines in the
// text storage. (Why 3? It seem Vim never allows less than 3 lines.)
//
// TODO: No need to search since line height is fixed, just calculate
// the new height.
int rowCount = maxRows;
int rowsToRemove;
for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
float height = cellSize.height*rowCount;
if (height <= size.height) {
fitSize.height = height;
break;
}
--rowCount;
}
fitRows -= rowsToRemove;
} else if (size.height > curSize.height) {
float fh = cellSize.height;
if (fh < 1.0f) fh = 1.0f;
fitRows = floor(size.height/fh);
fitSize.height = fh*fitRows;
}
if (size.width != curSize.width) {
float fw = cellSize.width;
if (fw < 1.0f) fw = 1.0f;
fitCols = floor(size.width/fw);
fitSize.width = fw*fitCols;
}
if (rows) *rows = fitRows;
if (columns) *columns = fitCols;
return fitSize;
}
@end // MMTextStorage
@implementation MMTextStorage (Private)
- (void)lazyResize:(BOOL)force
{
int i;
// Do nothing if the dimensions are already right.
if (!force && actualRows == maxRows && actualColumns == maxColumns)
return;
NSRange oldRange = NSMakeRange(0, actualRows*(actualColumns+1));
actualRows = maxRows;
actualColumns = maxColumns;
NSDictionary *dict;
if (defaultBackgroundColor) {
dict = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
defaultBackgroundColor, NSBackgroundColorAttributeName, nil];
} else {
dict = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName, nil];
}
NSMutableString *rowString = [NSMutableString string];
for (i = 0; i < maxColumns; ++i) {
[rowString appendString:@" "];
}
[rowString appendString:@"\n"];
[emptyRowString release];
emptyRowString = [[NSAttributedString alloc] initWithString:rowString
attributes:dict];
[attribString release];
attribString = [[NSMutableAttributedString alloc] init];
for (i=0; i<maxRows; ++i) {
[attribString appendAttributedString:emptyRowString];
}
NSRange fullRange = NSMakeRange(0, [attribString length]);
[self edited:(NSTextStorageEditedCharacters|NSTextStorageEditedAttributes)
range:oldRange changeInLength:fullRange.length-oldRange.length];
}
@end // MMTextStorage (Private)