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