1/** <title>NSTextContainer</title> 2 3 Copyright (C) 1999 Free Software Foundation, Inc. 4 5 Author: Alexander Malmberg <alexander@malmberg.org> 6 Date: 2002-11-23 7 8 Author: Jonathan Gapen <jagapen@smithlab.chem.wisc.edu> 9 Date: 1999 10 11 This file is part of the GNUstep GUI Library. 12 13 This library is free software; you can redistribute it and/or 14 modify it under the terms of the GNU Lesser General Public 15 License as published by the Free Software Foundation; either 16 version 2 of the License, or (at your option) any later version. 17 18 This library is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 Lesser General Public License for more details. 22 23 You should have received a copy of the GNU Lesser General Public 24 License along with this library; see the file COPYING.LIB. 25 If not, see <http://www.gnu.org/licenses/> or write to the 26 Free Software Foundation, 51 Franklin Street, Fifth Floor, 27 Boston, MA 02110-1301, USA. 28*/ 29 30#import <Foundation/NSGeometry.h> 31#import <Foundation/NSNotification.h> 32#import <Foundation/NSDebug.h> 33#import "AppKit/NSLayoutManager.h" 34#import "AppKit/NSTextContainer.h" 35#import "AppKit/NSTextStorage.h" 36#import "AppKit/NSTextView.h" 37#import "GNUstepGUI/GSLayoutManager.h" 38#import "GSGuiPrivate.h" 39 40@interface NSTextContainer (TextViewObserver) 41- (void) _textViewFrameChanged: (NSNotification*)aNotification; 42@end 43 44 45/* TODO: rethink how this is is triggered. 46use bounds rectangle instead of frame? */ 47@implementation NSTextContainer (TextViewObserver) 48 49- (void) _textViewFrameChanged: (NSNotification*)aNotification 50{ 51 if (_observingFrameChanges) 52 { 53 id textView; 54 NSSize newTextViewSize; 55 NSSize size; 56 NSSize inset; 57 58 textView = [aNotification object]; 59 if (textView != _textView) 60 { 61 NSDebugLog(@"NSTextContainer got notification for wrong View %@", 62 textView); 63 return; 64 } 65 newTextViewSize = [textView frame].size; 66 size = _containerRect.size; 67 inset = [textView textContainerInset]; 68 69 if (_widthTracksTextView) 70 { 71 size.width = MAX(newTextViewSize.width - (inset.width * 2.0), 0.0); 72 } 73 if (_heightTracksTextView) 74 { 75 size.height = MAX(newTextViewSize.height - (inset.height * 2.0), 0.0); 76 } 77 78 [self setContainerSize: size]; 79 } 80} 81 82@end /* NSTextContainer (TextViewObserver) */ 83 84@implementation NSTextContainer 85 86+ (void) initialize 87{ 88 if (self == [NSTextContainer class]) 89 { 90 [self setVersion: 1]; 91 } 92} 93 94- (id) initWithContainerSize: (NSSize)aSize 95{ 96 NSDebugLLog(@"NSText", @"NSTextContainer initWithContainerSize"); 97 if (aSize.width < 0) 98 { 99 NSWarnMLog(@"given negative width"); 100 aSize.width = 0; 101 } 102 if (aSize.height < 0) 103 { 104 NSWarnMLog(@"given negative height"); 105 aSize.height = 0; 106 } 107 _layoutManager = nil; 108 _textView = nil; 109 _containerRect.size = aSize; 110 // Tests on Cocoa indicate the default value is 5. 111 _lineFragmentPadding = 5.0; 112 _observingFrameChanges = NO; 113 _widthTracksTextView = NO; 114 _heightTracksTextView = NO; 115 116 return self; 117} 118 119- (id) init 120{ 121 return [self initWithContainerSize: NSMakeSize(1e7, 1e7)]; 122} 123 124- (void) dealloc 125{ 126 if (_textView != nil) 127 { 128 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 129 [nc removeObserver: self 130 name: NSViewFrameDidChangeNotification 131 object: _textView]; 132 133 [_textView setTextContainer: nil]; 134 RELEASE(_textView); 135 } 136 [super dealloc]; 137} 138 139/* 140See [NSTextView -setTextContainer:] for more information about these calls. 141*/ 142- (void) setLayoutManager: (GSLayoutManager*)aLayoutManager 143{ 144 /* The layout manager owns us - so he retains us and we don't retain 145 him. */ 146 _layoutManager = aLayoutManager; 147 /* Tell our text view about the change. */ 148 [_textView setTextContainer: self]; 149} 150 151- (GSLayoutManager*) layoutManager 152{ 153 return _layoutManager; 154} 155 156/* 157Replaces the layout manager while maintaining the text object 158framework intact. 159*/ 160- (void) replaceLayoutManager: (GSLayoutManager*)aLayoutManager 161{ 162 if (aLayoutManager != _layoutManager) 163 { 164 NSTextStorage *textStorage = [_layoutManager textStorage]; 165 NSArray *textContainers = [_layoutManager textContainers]; 166 NSUInteger i, count = [textContainers count]; 167 GSLayoutManager *oldLayoutManager = _layoutManager; 168 169 RETAIN(oldLayoutManager); 170 RETAIN(textStorage); 171 [textStorage removeLayoutManager: _layoutManager]; 172 [textStorage addLayoutManager: aLayoutManager]; 173 174 for (i = 0; i < count; i++) 175 { 176 NSTextContainer *container; 177 178 container = RETAIN([textContainers objectAtIndex: i]); 179 [oldLayoutManager removeTextContainerAtIndex: i]; 180 /* One of these calls will result in our _layoutManager being 181 changed. */ 182 [aLayoutManager addTextContainer: container]; 183 184 /* The textview is caching the layout manager; refresh the 185 * cache with this do-nothing call. */ 186 /* TODO: probably unnecessary; the call in -setLayoutManager: 187 should be enough */ 188 [[container textView] setTextContainer: container]; 189 RELEASE(container); 190 } 191 RELEASE(textStorage); 192 RELEASE(oldLayoutManager); 193 } 194} 195 196- (void) setTextView: (NSTextView*)aTextView 197{ 198 NSNotificationCenter *nc; 199 200 nc = [NSNotificationCenter defaultCenter]; 201 202 if (_textView) 203 { 204 [_textView setTextContainer: nil]; 205 [nc removeObserver: self name: NSViewFrameDidChangeNotification 206 object: _textView]; 207 /* NB: We do not set posts frame change notifications for the 208 text view to NO because there could be other observers for 209 the frame change notifications. */ 210 } 211 212 ASSIGN(_textView, aTextView); 213 214 if (aTextView != nil) 215 { 216 [_textView setTextContainer: self]; 217 if (_observingFrameChanges) 218 { 219 [_textView setPostsFrameChangedNotifications: YES]; 220 [nc addObserver: self 221 selector: @selector(_textViewFrameChanged:) 222 name: NSViewFrameDidChangeNotification 223 object: _textView]; 224 [self _textViewFrameChanged: 225 [NSNotification notificationWithName: NSViewFrameDidChangeNotification 226 object: _textView]]; 227 } 228 } 229 230 /* If someone's trying to set a NSTextView for us, the layout manager we 231 have must be capable of handling NSTextView:s. */ 232 [(NSLayoutManager *)_layoutManager textContainerChangedTextView: self]; 233} 234 235- (NSTextView*) textView 236{ 237 return _textView; 238} 239 240- (void) setContainerSize: (NSSize)aSize 241{ 242 if (NSEqualSizes(_containerRect.size, aSize)) 243 { 244 return; 245 } 246 247 if (aSize.width < 0) 248 { 249 NSWarnMLog(@"given negative width"); 250 aSize.width = 0; 251 } 252 if (aSize.height < 0) 253 { 254 NSWarnMLog(@"given negative height"); 255 aSize.height = 0; 256 } 257 258 _containerRect = NSMakeRect(0, 0, aSize.width, aSize.height); 259 260 if (_layoutManager) 261 { 262 [_layoutManager textContainerChangedGeometry: self]; 263 } 264} 265 266- (NSSize) containerSize 267{ 268 return _containerRect.size; 269} 270 271- (void) setWidthTracksTextView: (BOOL)flag 272{ 273 NSNotificationCenter *nc; 274 BOOL old_observing = _observingFrameChanges; 275 276 _widthTracksTextView = flag; 277 _observingFrameChanges = _widthTracksTextView | _heightTracksTextView; 278 279 if (_textView == nil) 280 return; 281 282 if (_observingFrameChanges == old_observing) 283 return; 284 285 nc = [NSNotificationCenter defaultCenter]; 286 287 if (_observingFrameChanges) 288 { 289 [_textView setPostsFrameChangedNotifications: YES]; 290 [nc addObserver: self 291 selector: @selector(_textViewFrameChanged:) 292 name: NSViewFrameDidChangeNotification 293 object: _textView]; 294 } 295 else 296 { 297 [nc removeObserver: self name: NSViewFrameDidChangeNotification 298 object: _textView]; 299 } 300} 301 302- (BOOL) widthTracksTextView 303{ 304 return _widthTracksTextView; 305} 306 307- (void) setHeightTracksTextView: (BOOL)flag 308{ 309 NSNotificationCenter *nc; 310 BOOL old_observing = _observingFrameChanges; 311 312 _heightTracksTextView = flag; 313 _observingFrameChanges = _widthTracksTextView | _heightTracksTextView; 314 if (_textView == nil) 315 return; 316 317 if (_observingFrameChanges == old_observing) 318 return; 319 320 nc = [NSNotificationCenter defaultCenter]; 321 322 if (_observingFrameChanges) 323 { 324 [_textView setPostsFrameChangedNotifications: YES]; 325 [nc addObserver: self 326 selector: @selector(_textViewFrameChanged:) 327 name: NSViewFrameDidChangeNotification 328 object: _textView]; 329 } 330 else 331 { 332 [nc removeObserver: self name: NSViewFrameDidChangeNotification 333 object: _textView]; 334 } 335} 336 337- (BOOL) heightTracksTextView 338{ 339 return _heightTracksTextView; 340} 341 342- (void) setLineFragmentPadding: (CGFloat)aFloat 343{ 344 _lineFragmentPadding = aFloat; 345 346 if (_layoutManager) 347 [_layoutManager textContainerChangedGeometry: self]; 348} 349 350- (CGFloat) lineFragmentPadding 351{ 352 return _lineFragmentPadding; 353} 354 355- (NSRect) lineFragmentRectForProposedRect: (NSRect)proposedRect 356 sweepDirection: (NSLineSweepDirection)sweepDir 357 movementDirection: (NSLineMovementDirection)moveDir 358 remainingRect: (NSRect *)remainingRect 359{ 360 CGFloat minx, maxx, miny, maxy; 361 CGFloat cminx, cmaxx, cminy, cmaxy; 362 363 minx = NSMinX(proposedRect); 364 maxx = NSMaxX(proposedRect); 365 miny = NSMinY(proposedRect); 366 maxy = NSMaxY(proposedRect); 367 368 cminx = NSMinX(_containerRect) + _lineFragmentPadding; 369 cmaxx = NSMaxX(_containerRect) - _lineFragmentPadding; 370 cminy = NSMinY(_containerRect); 371 cmaxy = NSMaxY(_containerRect); 372 373 *remainingRect = NSZeroRect; 374 375 if (minx >= cminx && maxx <= cmaxx && miny >= cminy && maxy <= cmaxy) 376 { 377 return proposedRect; 378 } 379 380 switch (moveDir) 381 { 382 case NSLineMovesLeft: 383 if (maxx < cminx) 384 return NSZeroRect; 385 if (maxx > cmaxx) 386 { 387 minx -= maxx-cmaxx; 388 maxx = cmaxx; 389 } 390 break; 391 392 case NSLineMovesRight: 393 if (minx > cmaxx) 394 return NSZeroRect; 395 if (minx < cminx) 396 { 397 maxx += cminx-minx; 398 minx = cminx; 399 } 400 break; 401 402 case NSLineMovesDown: 403 if (miny > cmaxy) 404 return NSZeroRect; 405 if (miny < cminy) 406 { 407 maxy += cminy - miny; 408 miny = cminy; 409 } 410 break; 411 412 case NSLineMovesUp: 413 if (maxy < cminy) 414 return NSZeroRect; 415 if (maxy > cmaxy) 416 { 417 miny -= maxy - cmaxy; 418 maxy = cmaxy; 419 } 420 break; 421 422 case NSLineDoesntMove: 423 break; 424 } 425 426 switch (sweepDir) 427 { 428 case NSLineSweepLeft: 429 case NSLineSweepRight: 430 if (minx < cminx) 431 minx = cminx; 432 if (maxx > cmaxx) 433 maxx = cmaxx; 434 break; 435 436 case NSLineSweepDown: 437 case NSLineSweepUp: 438 if (miny < cminy) 439 miny = cminy; 440 if (maxy > cmaxy) 441 maxy = cmaxy; 442 break; 443 } 444 445 if (minx < cminx || maxx > cmaxx || 446 miny < cminy || maxy > cmaxy) 447 { 448 return NSZeroRect; 449 } 450 451 return NSMakeRect(minx, miny, 452 (maxx > minx) ? maxx - minx : 0.0, 453 (maxy > miny) ? maxy - miny : 0.0); 454} 455 456- (BOOL) isSimpleRectangularTextContainer 457{ 458 // sub-classes may say no; this class always says yes 459 return YES; 460} 461 462- (BOOL) containsPoint: (NSPoint)aPoint 463{ 464 return NSPointInRect(aPoint, _containerRect); 465} 466 467- (id) initWithCoder: (NSCoder*)aDecoder 468{ 469 if ([aDecoder allowsKeyedCoding]) 470 { 471 NSSize size = NSMakeSize(1e7, 1e7); 472 473 if ([aDecoder containsValueForKey: @"NSWidth"]) 474 { 475 size.width = [aDecoder decodeFloatForKey: @"NSWidth"]; 476 } 477 self = [self initWithContainerSize: size]; 478 if ([aDecoder containsValueForKey: @"NSTCFlags"]) 479 { 480 int flags = [aDecoder decodeIntForKey: @"NSTCFlags"]; 481 482 // decode the flags. 483 _widthTracksTextView = (flags & 1) != 0; 484 _heightTracksTextView = (flags & 2) != 0; 485 // Mac OS X doesn't seem to save this flag 486 _observingFrameChanges = _widthTracksTextView | _heightTracksTextView; 487 } 488 489 // decoding the manager adds this text container automatically... 490 if ([aDecoder containsValueForKey: @"NSLayoutManager"]) 491 { 492 _layoutManager = [aDecoder decodeObjectForKey: @"NSLayoutManager"]; 493 } 494 495 return self; 496 } 497 else 498 { 499 return self; 500 } 501} 502 503- (void) encodeWithCoder: (NSCoder *)coder 504{ 505 if ([coder allowsKeyedCoding]) 506 { 507 NSSize size = _containerRect.size; 508 int flags = ((_widthTracksTextView)?1:0) | 509 ((_heightTracksTextView)?2:0) | 510 ((_observingFrameChanges)?4:0); 511 512 [coder encodeObject: _textView forKey: @"NSTextView"]; 513 [coder encodeObject: _layoutManager forKey: @"NSLayoutManager"]; 514 [coder encodeFloat: size.width forKey: @"NSWidth"]; 515 [coder encodeInt: flags forKey: @"NSTCFlags"]; 516 } 517} 518 519@end /* NSTextContainer */ 520 521