/**
Sets aView the NSClipView's document view to aView .TODO explain notifications ...
See Also: -documentView
*/ - (void) setDocumentView: (NSView*)aView { NSNotificationCenter *nc; if (_documentView == aView) { return; } nc = [NSNotificationCenter defaultCenter]; if (_documentView) { [nc removeObserver: self name: nil object: _documentView]; /* if our documentView was a tableview, unregister its * observers */ if ([_documentView isKindOfClass: [NSTableView class]]) { [nc removeObserver: _documentView name: nil object: self]; } [_documentView removeFromSuperview]; } /* Don't retain this since it's stored in our subviews. */ _documentView = aView; /* Call this before doing anything else ! */ _rFlags.flipped_view = [self isFlipped]; [self _invalidateCoordinates]; if (_documentView) { NSRect df; [self addSubview: _documentView]; df = [_documentView frame]; [self setBoundsOrigin: df.origin]; if ([aView respondsToSelector: @selector(backgroundColor)]) { [self setBackgroundColor: [(id)aView backgroundColor]]; } if ([aView respondsToSelector: @selector(drawsBackground)]) { [self setDrawsBackground: [(id)aView drawsBackground]]; } /* Register for notifications sent by the document view */ [_documentView setPostsFrameChangedNotifications: YES]; [_documentView setPostsBoundsChangedNotifications: YES]; [nc addObserver: self selector: @selector(viewFrameChanged:) name: NSViewFrameDidChangeNotification object: _documentView]; [nc addObserver: self selector: @selector(viewBoundsChanged:) name: NSViewBoundsDidChangeNotification object: _documentView]; /* * if out document view is a tableview, let it know * when we resize */ if ([_documentView isKindOfClass: [NSTableView class]]) { [nc removeObserver: _documentView name: nil object: self]; [self setPostsFrameChangedNotifications: YES]; [nc addObserver: _documentView selector: @selector(superviewFrameChanged:) name: NSViewFrameDidChangeNotification object: self]; } } /* TODO: Adjust the key view loop to include the new document view */ [_super_view reflectScrolledClipView: self]; } - (void) resetCursorRects { [self addCursorRect: _bounds cursor: _cursor]; } - (void) scrollToPoint: (NSPoint)aPoint { [self setBoundsOrigin: [self constrainScrollPoint: aPoint]]; } - (void) setBoundsOrigin: (NSPoint)aPoint { NSRect originalBounds = _bounds; NSRect newBounds = originalBounds; NSRect intersection; newBounds.origin = aPoint; if (NSEqualPoints (originalBounds.origin, newBounds.origin)) { return; } if (_documentView == nil) { return; } if (_copiesOnScroll && _window && [_window gState]) { /* Copy the portion of the view that is common before and after scrolling. Then, document view needs to redraw the remaining areas. */ /* Common part - which is a first approx of what we could copy... */ intersection = NSIntersectionRect (originalBounds, newBounds); /* but we must make sure we only copy from visible rect - we can't copy bits which have been clipped (ie discarded) */ intersection = NSIntersectionRect (intersection, [self visibleRect]); /* Copying is done in device space so we only can copy by integral rects in device space - adjust our copy rect */ intersection = integralRect (intersection, self); /* At this point, intersection is the rectangle containing the image we can recycle from the old to the new situation. We must not make any assumption on its position/size, because it has been intersected with visible rect, which is an arbitrary rectangle as far as we know. */ if (NSEqualRects (intersection, NSZeroRect)) { // no recyclable part -- docview should redraw everything // from scratch [super setBoundsOrigin: newBounds.origin]; [_documentView setNeedsDisplayInRect: [self documentVisibleRect]]; } else { /* Copy the intersection to the new position */ NSPoint destPoint = intersection.origin; float dx = newBounds.origin.x - originalBounds.origin.x; float dy = newBounds.origin.y - originalBounds.origin.y; NSRect redrawRect; /* It is assumed these dx and dy will be integer in device space because they are the difference of the bounds origins, both of which should be integers in device space because of the code at the end of constrainScrollPoint:. */ destPoint.x -= dx; destPoint.y -= dy; /* Now copy ! */ [self lockFocus]; /* NB: Because of all the previous comments, we are sure the following is copying an integer rectangle by an integer amount (`integer' in device space) - which should cause no problems */ NSCopyBits (0, intersection, destPoint); [self unlockFocus]; /* Change coordinate system to the new one */ [super setBoundsOrigin: newBounds.origin]; /* Get the rectangle representing intersection in the new bounds (mainly to keep code readable) */ intersection.origin.x = destPoint.x; intersection.origin.y = destPoint.y; // intersection.size is the same /* Now mark everything which is outside intersection as needing to be redrawn by hand. NB: During simple usage - scrolling in a single direction (left/rigth/up/down) - and a normal visible rect, only one of the following rects will be non-empty. */ /* To the left of intersection */ redrawRect = NSMakeRect (NSMinX (_bounds), _bounds.origin.y, NSMinX (intersection) - NSMinX (_bounds), _bounds.size.height); if (NSIsEmptyRect (redrawRect) == NO) { [_documentView setNeedsDisplayInRect: [self convertRect: redrawRect toView: _documentView]]; } /* Right */ redrawRect = NSMakeRect (NSMaxX (intersection), _bounds.origin.y, NSMaxX (_bounds) - NSMaxX (intersection), _bounds.size.height); if (NSIsEmptyRect (redrawRect) == NO) { [_documentView setNeedsDisplayInRect: [self convertRect: redrawRect toView: _documentView]]; } /* Up (or Down according to whether it's flipped or not) */ redrawRect = NSMakeRect (_bounds.origin.x, NSMinY (_bounds), _bounds.size.width, NSMinY (intersection) - NSMinY (_bounds)); if (NSIsEmptyRect (redrawRect) == NO) { [_documentView setNeedsDisplayInRect: [self convertRect: redrawRect toView: _documentView]]; } /* Down (or Up) */ redrawRect = NSMakeRect (_bounds.origin.x, NSMaxY (intersection), _bounds.size.width, NSMaxY (_bounds) - NSMaxY (intersection)); if (NSIsEmptyRect (redrawRect) == NO) { [_documentView setNeedsDisplayInRect: [self convertRect: redrawRect toView: _documentView]]; } } } else { // dont copy anything -- docview draws it all [super setBoundsOrigin: newBounds.origin]; [_documentView setNeedsDisplayInRect: [self documentVisibleRect]]; } /* ?? TODO: Understand the following code - and add explanatory comment */ if ([NSView focusView] == _documentView) { PStranslate (NSMinX (originalBounds) - aPoint.x, NSMinY (originalBounds) - aPoint.y); } [_super_view reflectScrolledClipView: self]; } /** *TODO
*/ - (NSPoint) constrainScrollPoint: (NSPoint)proposedNewOrigin { NSRect documentFrame; NSPoint new = proposedNewOrigin; if (_documentView == nil) { return _bounds.origin; } documentFrame = [_documentView frame]; if (documentFrame.size.width <= _bounds.size.width) { new.x = documentFrame.origin.x; } else if (proposedNewOrigin.x <= documentFrame.origin.x) { new.x = documentFrame.origin.x; } else if (proposedNewOrigin.x + _bounds.size.width >= NSMaxX(documentFrame)) { new.x = NSMaxX(documentFrame) - _bounds.size.width; } if (documentFrame.size.height <= _bounds.size.height) { new.y = documentFrame.origin.y; } else if (proposedNewOrigin.y <= documentFrame.origin.y) { new.y = documentFrame.origin.y; } else if (proposedNewOrigin.y + _bounds.size.height >= NSMaxY(documentFrame)) { new.y = NSMaxY(documentFrame) - _bounds.size.height; } /* Make it an integer coordinate in device space - this is to make sure that when the coordinates are changed and we need to copy to do the scrolling, the difference is an integer and so we can copy the image translating it by an integer in device space - and not by a float. */ /* new = [self convertPoint: new toView: nil]; new.x = (int)new.x; new.y = (int)new.y; new = [self convertPoint: new fromView: nil]; */ /* We don't make it an integer this way anymore. This is not needed when _copiesOnScroll is not set. If _copiesOnScroll is set, we make sure the difference between old position and new position is an integer so we can copy the image easily. */ if (_copiesOnScroll) { new.x = _bounds.origin.x + (rint(new.x - _bounds.origin.x)); new.y = _bounds.origin.y + (rint(new.y - _bounds.origin.y)); } return new; } /**Returns the document rectangle.
See Also: -documentVisibleRect
*/ - (NSRect) documentRect { NSRect documentFrame; NSRect clipViewBounds; NSRect rect; if (_documentView == nil) { return _bounds; } documentFrame = [_documentView frame]; clipViewBounds = _bounds; rect.origin = documentFrame.origin; rect.size.width = MAX(documentFrame.size.width, clipViewBounds.size.width); rect.size.height = MAX(documentFrame.size.height, clipViewBounds.size.height); return rect; } /**Returns the document visible rectangle
See Also: -documentRect
*/ - (NSRect) documentVisibleRect { NSRect documentBounds; NSRect clipViewBounds; NSRect rect; if (_documentView == nil) { return NSZeroRect; } documentBounds = [_documentView bounds]; clipViewBounds = [self convertRect: _bounds toView: _documentView]; rect = NSIntersectionRect (documentBounds, clipViewBounds); return rect; } - (void) drawRect: (NSRect)rect { if (_drawsBackground) { [_backgroundColor set]; NSRectFill(rect); } } /**Scrolls in response to mouse-dragged events.
*/ - (BOOL) autoscroll: (NSEvent*)theEvent { NSPoint new; NSPoint delta; NSRect r; if (_documentView == nil) { return NO; } new = [_documentView convertPoint: [theEvent locationInWindow] fromView: nil]; r = [self documentVisibleRect]; if (new.x < NSMinX(r)) delta.x = new.x - NSMinX(r); else if (new.x > NSMaxX(r)) delta.x = new.x - NSMaxX(r); else delta.x = 0; if (new.y < NSMinY(r)) delta.y = new.y - NSMinY(r); else if (new.y > NSMaxY(r)) delta.y = new.y - NSMaxY(r); else delta.y = 0; new.x = _bounds.origin.x + delta.x; new.y = _bounds.origin.y + delta.y; new = [self constrainScrollPoint: new]; if (NSEqualPoints(new, _bounds.origin)) return NO; [self setBoundsOrigin: new]; return YES; } - (void) viewBoundsChanged: (NSNotification*)aNotification { [_super_view reflectScrolledClipView: self]; } /**Used when the document view frame notify its change. ( with NSViewFrameDidChangeNotification )
*/ - (void) viewFrameChanged: (NSNotification*)aNotification { [self setBoundsOrigin: [self constrainScrollPoint: _bounds.origin]]; /* If document frame does not completely cover _bounds */ if (NSContainsRect([_documentView frame], _bounds) == NO) { /* * fill the area not covered by documentView with background color */ [self setNeedsDisplay: YES]; } [_super_view reflectScrolledClipView: self]; } - (void) scaleUnitSquareToSize: (NSSize)newUnitSize { [super scaleUnitSquareToSize: newUnitSize]; [_super_view reflectScrolledClipView: self]; } - (void) setBoundsSize: (NSSize)aSize { [super setBoundsSize: aSize]; [_super_view reflectScrolledClipView: self]; } - (void) setFrameSize: (NSSize)aSize { [super setFrameSize: aSize]; [self setBoundsOrigin: [self constrainScrollPoint: _bounds.origin]]; [_super_view reflectScrolledClipView: self]; } - (void) setFrameOrigin: (NSPoint)aPoint { [super setFrameOrigin: aPoint]; [self setBoundsOrigin: [self constrainScrollPoint: _bounds.origin]]; [_super_view reflectScrolledClipView: self]; } - (void) setFrame: (NSRect)rect { [super setFrame: rect]; [self setBoundsOrigin: [self constrainScrollPoint: _bounds.origin]]; [_super_view reflectScrolledClipView: self]; } - (void) translateOriginToPoint: (NSPoint)aPoint { [super translateOriginToPoint: aPoint]; [_super_view reflectScrolledClipView: self]; } /** *Returns the NSClipView's document view
*See Also: -setDocumentView:
*/ - (id) documentView { return _documentView; } - (void) setCopiesOnScroll: (BOOL)flag { _copiesOnScroll = flag; } - (BOOL) copiesOnScroll { return _copiesOnScroll; } /**Sets the cursor for the document view to aCursor
See Also: -documentCursor
*/ - (void) setDocumentCursor: (NSCursor*)aCursor { ASSIGN (_cursor, aCursor); } /**Returns the cursor of the document view
See Also: -setDocumentCursor:
*/ - (NSCursor*) documentCursor { return _cursor; } /**Returns the NSClipView's background color
See Also: -setBackgroundColor:
*/ - (NSColor*) backgroundColor { return _backgroundColor; } /**Sets the NSClipView's background color to aColor and marks self for display. Sets the opaque flag to if needed
See Also: -backgroundColor [NSView-isOpaque]
*/ - (void) setBackgroundColor: (NSColor*)aColor { if (![_backgroundColor isEqual: aColor]) { ASSIGN (_backgroundColor, aColor); [self setNeedsDisplay: YES]; if (_drawsBackground == NO || _backgroundColor == nil || [_backgroundColor alphaComponent] < 1.0) { _isOpaque = NO; } else { _isOpaque = YES; } } } - (void) setDrawsBackground:(BOOL)flag { if (_drawsBackground != flag) { _drawsBackground = flag; [self setNeedsDisplay: YES]; if (_drawsBackground == NO || _backgroundColor == nil || [_backgroundColor alphaComponent] < 1.0) { _isOpaque = NO; } else { _isOpaque = YES; } } } - (BOOL) drawsBackground { return _drawsBackground; } - (BOOL) isOpaque { return _isOpaque; } - (BOOL) isFlipped { return (_documentView != nil) ? _documentView->_rFlags.flipped_view : NO; } /* Disable rotation of clip view */ - (void) rotateByAngle: (float)angle { } - (void) setBoundsRotation: (float)angle { } - (void) setFrameRotation: (float)angle { } /* Managing responder chain */ - (BOOL) acceptsFirstResponder { if (_documentView == nil) { return NO; } else { return [_documentView acceptsFirstResponder]; } } - (BOOL) becomeFirstResponder { if (_documentView == nil) { return NO; } else { return [_window makeFirstResponder: _documentView]; } } /* * NSCoding protocol */ - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; [aCoder encodeObject: _backgroundColor]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_copiesOnScroll]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_drawsBackground]; [aCoder encodeObject: _cursor]; } - (id) initWithCoder: (NSCoder*)aDecoder { self = [super initWithCoder: aDecoder]; if ([aDecoder allowsKeyedCoding]) { int flags; [self setBackgroundColor: [aDecoder decodeObjectForKey: @"NSBGColor"]]; [self setDocumentCursor: [aDecoder decodeObjectForKey: @"NSCursor"]]; [self setDocumentView: [aDecoder decodeObjectForKey: @"NSDocView"]]; if ([aDecoder containsValueForKey: @"NScvFlags"]) { flags = [aDecoder decodeIntForKey: @"NScvFlags"]; // FIXME setCopiesOnScroll: setDrawsBackground: } } else { NSView *document; BOOL temp; [self setAutoresizesSubviews: YES]; [self setBackgroundColor: [aDecoder decodeObject]]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_copiesOnScroll]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &temp]; [self setDrawsBackground: temp]; [aDecoder decodeValueOfObjCType: @encode(id) at: &_cursor]; if ([[self subviews] count] > 0) { document = AUTORELEASE(RETAIN([[self subviews] objectAtIndex: 0])); [self removeSubview: document]; [self setDocumentView: document]; } } return self; } @end