libs-gui/Source/NSScrollView.m
Eric Wasylishen 310b7d5786 * Headers/Additions/GNUstepGUI/GSTheme.h: Remove GSScrollViewBottomCorner
part name, instead themes should just provide a part called NSScrollView.

	Add -scrollViewScrollersOverlapBorders method.

	* Source/GSTheme.m: Remove GSScrollViewBottomCorner part name.
	* Source/GSThemeDrawing.m: Add -scrollViewScrollersOverlapBorders.
	* Source/GSThemeDrawing.m (-drawBrowserRect:...): If
	-scrollViewScrollersOverlapBorders is enabled, fill the browser background
	with the NSScrollView tile.
	* Source/GSThemeDrawing.m (-drawScrollViewRect:...): If
	-scrollViewScrollersOverlapBorders is enabled, fill the scroll view background
	with the NSScrollView tile.
	* Source/NSScroller.m (-rectForPart:): Change the meaning of the
	GSScrollerKnobOvershoot default so the knob only overlaps the buttons
	by this much (rather than both ends of the track). Turns out this is more
	useful for themes.
	* Source/NSScrollView.m (-tile): Add support for
	-[GSTheme scrollViewScrollersOverlapBorders]
	* Source/NSBrowser.m (-tile): Add support for
	-[GSTheme scrollViewScrollersOverlapBorders] and
	-[GSTheme scrollViewUseBottomCorner]

	The overall point of these additions is to support NSScrollView and
	NSBrowser looking like: http://jesseross.com/clients/gnustep/ui/concepts/


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@37238 72102866-910b-0410-8b05-ffd578937521
2013-10-15 23:26:51 +00:00

1804 lines
44 KiB
Objective-C

/** <title>NSScrollView</title>
Copyright (C) 1996 Free Software Foundation, Inc.
Author: Ovidiu Predescu <ovidiu@net-community.com>
Date: July 1997
Author: Felipe A. Rodriguez <far@ix.netcom.com>
Date: October 1998
Author: Richard Frith-Macdonald <richard@brainstorm.co.uk>
Date: February 1999
Table View Support: Nicola Pero <n.pero@mi.flashnet.it>
Date: March 2000
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import <Foundation/NSDebug.h>
#import <Foundation/NSException.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSUserDefaults.h>
#import "AppKit/NSColor.h"
#import "AppKit/NSColorList.h"
#import "AppKit/NSCell.h"
#import "AppKit/NSClipView.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSInterfaceStyle.h"
#import "AppKit/NSRulerView.h"
#import "AppKit/NSScroller.h"
#import "AppKit/NSScrollView.h"
#import "AppKit/NSTableHeaderView.h"
#import "AppKit/NSTableView.h"
#import "AppKit/NSWindow.h"
#import "AppKit/PSOperators.h"
#import "GNUstepGUI/GSTheme.h"
@interface NSClipView (Private)
- (void) _scrollToPoint: (NSPoint)aPoint;
@end
//
// For nib compatibility, this is used to properly
// initialize the object from a OS X nib file in initWithCoder:.
//
typedef struct _scrollViewFlags
{
#if GS_WORDS_BIGENDIAN == 1
unsigned int __unused6:14;
unsigned int __unused5:1;
unsigned int autohidesScrollers:1;
unsigned int __unused4:1;
unsigned int __unused3:1;
unsigned int __unused2:1;
unsigned int doesNotDrawBackground:1;
unsigned int __unused1:1;
unsigned int hasVRuler:1;
unsigned int hasHRuler:1;
unsigned int showRulers:1;
unsigned int oldRulerInstalled:1;
unsigned int nonDynamic:1;
unsigned int hasHScroller:1;
unsigned int hasVScroller:1;
unsigned int hScrollerRequired:1;
unsigned int vScrollerRequired:1;
NSBorderType border:2;
#else
NSBorderType border:2;
unsigned int vScrollerRequired:1;
unsigned int hScrollerRequired:1;
unsigned int hasVScroller:1;
unsigned int hasHScroller:1;
unsigned int nonDynamic:1;
unsigned int oldRulerInstalled:1;
unsigned int showRulers:1;
unsigned int hasHRuler:1;
unsigned int hasVRuler:1;
unsigned int __unused1:1;
unsigned int doesNotDrawBackground:1;
unsigned int __unused2:1;
unsigned int __unused3:1;
unsigned int __unused4:1;
unsigned int autohidesScrollers:1;
unsigned int __unused5:1;
unsigned int __unused6:14;
#endif
} GSScrollViewFlags;
@interface NSScrollView (GSPrivate)
/* GNUstep private methods */
- (void) _synchronizeHeaderAndCornerView;
- (void) _themeDidActivate: (NSNotification*)notification;
@end
@implementation NSScrollView
/*
* Class variables
*/
static Class rulerViewClass = nil;
static CGFloat scrollerWidth;
/*
* Class methods
*/
+ (void) initialize
{
if (self == [NSScrollView class])
{
[self setRulerViewClass: [NSRulerView class]];
scrollerWidth = [NSScroller scrollerWidth];
[self setVersion: 2];
}
}
+ (void) setRulerViewClass: (Class)aClass
{
rulerViewClass = aClass;
}
+ (Class) rulerViewClass
{
return rulerViewClass;
}
+ (NSSize) contentSizeForFrameSize: (NSSize)frameSize
hasHorizontalScroller: (BOOL)hFlag
hasVerticalScroller: (BOOL)vFlag
borderType: (NSBorderType)borderType
{
NSSize size = frameSize;
NSSize border = [[GSTheme theme] sizeForBorderType: borderType];
CGFloat innerBorderWidth = [[NSUserDefaults standardUserDefaults]
boolForKey: @"GSScrollViewNoInnerBorder"] ? 0.0 : 1.0;
/*
* Substract 1 from the width and height of
* the line that separates the horizontal
* and vertical scroller from the clip view
*/
if (hFlag)
{
size.height -= scrollerWidth + innerBorderWidth;
}
if (vFlag)
{
size.width -= scrollerWidth + innerBorderWidth;
}
size.width -= 2 * border.width;
size.height -= 2 * border.height;
return size;
}
+ (NSSize) frameSizeForContentSize: (NSSize)contentSize
hasHorizontalScroller: (BOOL)hFlag
hasVerticalScroller: (BOOL)vFlag
borderType: (NSBorderType)borderType
{
NSSize size = contentSize;
NSSize border = [[GSTheme theme] sizeForBorderType: borderType];
CGFloat innerBorderWidth = [[NSUserDefaults standardUserDefaults]
boolForKey: @"GSScrollViewNoInnerBorder"] ? 0.0 : 1.0;
/*
* Add 1 to the width and height for the line that separates the
* horizontal and vertical scroller from the clip view.
*/
if (hFlag)
{
size.height += scrollerWidth + innerBorderWidth;
}
if (vFlag)
{
size.width += scrollerWidth + innerBorderWidth;
}
size.width += 2 * border.width;
size.height += 2 * border.height;
return size;
}
/*
* Instance methods
*/
- (id) initWithFrame: (NSRect)rect
{
NSClipView *clipView;
self = [super initWithFrame: rect];
if (!self)
return nil;
clipView = [NSClipView new];
[self setContentView: clipView];
RELEASE(clipView);
_hLineScroll = 10;
_hPageScroll = 10;
_vLineScroll = 10;
_vPageScroll = 10;
_borderType = NSNoBorder;
_scrollsDynamically = YES;
//_autohidesScrollers = NO;
// FIXME: Not sure here Apple says by default all scrollers are off.
// For compatibility the ruler should be present but not visible.
[self setHasHorizontalRuler: YES];
[self tile];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_themeDidActivate:)
name: GSThemeDidActivateNotification
object: nil];
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
DESTROY(_horizScroller);
DESTROY(_vertScroller);
DESTROY(_horizRuler);
DESTROY(_vertRuler);
[super dealloc];
}
- (BOOL) isFlipped
{
return YES;
}
- (void) setContentView: (NSClipView *)aView
{
if (aView == nil)
[NSException raise: NSInvalidArgumentException
format: @"Attempt to set nil content view"];
if ([aView isKindOfClass: [NSView class]] == NO)
[NSException raise: NSInvalidArgumentException
format: @"Attempt to set non-view object as content view"];
if (aView != _contentView)
{
NSView *docView = [aView documentView];
[_contentView removeFromSuperview];
[self addSubview: aView];
// This must be done after adding it as a subview,
// otherwise it will get unset again.
_contentView = aView;
if (docView != nil)
{
[self setDocumentView: docView];
}
}
[_contentView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
[self tile];
}
- (void) willRemoveSubview: (NSView *)aView
{
if (aView == _contentView)
{
_contentView = nil;
}
if (aView == _headerClipView)
{
_headerClipView = nil;
}
if (aView == _cornerView)
{
_cornerView = nil;
}
[super willRemoveSubview: aView];
}
- (void) setHorizontalScroller: (NSScroller*)aScroller
{
[_horizScroller removeFromSuperview];
/*
* Do not add the scroller view to the subviews array yet;
* -setHasHorizontalScroller must be invoked first
*/
ASSIGN(_horizScroller, aScroller);
if (_horizScroller)
{
[_horizScroller setAutoresizingMask: NSViewWidthSizable];
[_horizScroller setTarget: self];
[_horizScroller setAction: @selector(_doScroll:)];
}
}
- (void) setHasHorizontalScroller: (BOOL)flag
{
if (_hasHorizScroller == flag)
return;
_hasHorizScroller = flag;
if (_hasHorizScroller)
{
if (!_horizScroller)
{
NSScroller *scroller = [NSScroller new];
[self setHorizontalScroller: scroller];
RELEASE(scroller);
}
[self addSubview: _horizScroller];
}
else
[_horizScroller removeFromSuperview];
[self tile];
}
- (void) setVerticalScroller: (NSScroller*)aScroller
{
[_vertScroller removeFromSuperview];
/*
* Do not add the scroller view to the subviews array yet;
* -setHasVerticalScroller must be invoked first
*/
ASSIGN(_vertScroller, aScroller);
if (_vertScroller)
{
[_vertScroller setAutoresizingMask: NSViewHeightSizable];
[_vertScroller setTarget: self];
[_vertScroller setAction: @selector(_doScroll:)];
}
}
- (void) setHasVerticalScroller: (BOOL)flag
{
if (_hasVertScroller == flag)
return;
_hasVertScroller = flag;
if (_hasVertScroller)
{
if (!_vertScroller)
{
NSScroller *scroller = [NSScroller new];
[self setVerticalScroller: scroller];
RELEASE(scroller);
if (_contentView && ![_contentView isFlipped])
[_vertScroller setFloatValue: 1];
}
[self addSubview: _vertScroller];
}
else
[_vertScroller removeFromSuperview];
[self tile];
}
/**
<p> Return wether scroller autohiding is set or not. </p>
<p>See Also: -setAutohidesScrollers:</p>
*/
- (BOOL) autohidesScrollers
{
return _autohidesScrollers;
}
/**
<p>Sets whether the view hides the scrollers (horizontal and/or vertical independendently) if they are not needed.</p>
<p>If the content fits inside the clip view on the X or Y axis or both, the respective scroller is removed and additional space is gained.</p>
<p>See Also: -autohidesScrollers</p>
*/
- (void) setAutohidesScrollers: (BOOL)flag
{
_autohidesScrollers = flag;
}
- (void) scrollWheel: (NSEvent *)theEvent
{
NSRect clipViewBounds;
CGFloat deltaY = [theEvent deltaY];
CGFloat deltaX = [theEvent deltaX];
CGFloat amount;
NSPoint point;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
}
point = clipViewBounds.origin;
// Holding shift converts vertical scrolling to horizontal
if (([theEvent modifierFlags] & NSShiftKeyMask) == NSShiftKeyMask)
{
deltaX = -deltaY;
deltaY = 0;
}
// Scroll horizontally
if (([theEvent modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask)
{
amount = (clipViewBounds.size.width - _hPageScroll) * deltaX;
}
else
{
amount = _hLineScroll * deltaX;
}
NSDebugLLog (@"NSScrollView",
@"increment/decrement: amount = %f, horizontal", amount);
point.x = clipViewBounds.origin.x + amount;
// Scroll vertically
if (([theEvent modifierFlags] & NSAlternateKeyMask) == NSAlternateKeyMask)
{
amount = - (clipViewBounds.size.height - _vPageScroll) * deltaY;
}
else
{
amount = - _vLineScroll * deltaY;
}
if (_contentView != nil && ![_contentView isFlipped])
{
/* If view is flipped reverse the scroll direction */
amount = -amount;
}
NSDebugLLog (@"NSScrollView",
@"increment/decrement: amount = %f, flipped = %d",
amount, _contentView ? [_contentView isFlipped] : 0);
point.y = clipViewBounds.origin.y + amount;
/* scrollToPoint: will call reflectScrolledClipView:, which will
* update rules, headers, and scrollers. */
[_contentView _scrollToPoint: point];
}
- (void) keyDown: (NSEvent *)theEvent
{
NSString *chars = [theEvent characters];
unichar c = [chars length] == 1 ? [chars characterAtIndex: 0] : '\0';
switch (c)
{
case NSUpArrowFunctionKey:
[self scrollLineUp: self];
break;
case NSDownArrowFunctionKey:
[self scrollLineDown: self];
break;
case NSPageUpFunctionKey:
[self scrollPageUp: self];
break;
case NSPageDownFunctionKey:
[self scrollPageDown: self];
break;
default:
[super keyDown: theEvent];
break;
}
}
/*
* This code is based on _doScroll: and still may need some tuning.
*/
- (void) scrollLineUp: (id)sender
{
NSRect clipViewBounds;
NSPoint point;
CGFloat amount;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
}
point = clipViewBounds.origin;
amount = _vLineScroll;
if (_contentView != nil && ![_contentView isFlipped])
{
amount = -amount;
}
point.y = clipViewBounds.origin.y - amount;
[_contentView _scrollToPoint: point];
}
/*
* This code is based on _doScroll: and still may need some tuning.
*/
- (void) scrollLineDown: (id)sender
{
NSRect clipViewBounds;
NSPoint point;
CGFloat amount;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
}
point = clipViewBounds.origin;
amount = _vLineScroll;
if (_contentView != nil && ![_contentView isFlipped])
{
amount = -amount;
}
point.y = clipViewBounds.origin.y + amount;
[_contentView _scrollToPoint: point];
}
/**
* Scrolls the receiver by simply invoking scrollPageUp:
*/
- (void) pageUp: (id)sender
{
[self scrollPageUp: sender];
}
/*
* This code is based on _doScroll: and still may need some tuning.
*/
- (void) scrollPageUp: (id)sender
{
NSRect clipViewBounds;
NSPoint point;
CGFloat amount;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
}
point = clipViewBounds.origin;
/*
* Take verticalPageScroll into accout, but try to make sure
* that amount is never negative (ie do not scroll backwards.)
*
* FIXME: It seems _doScroll and scrollWheel: should also take
* care not to do negative scrolling.
*/
amount = clipViewBounds.size.height - _vPageScroll;
amount = (amount < 0) ? 0 : amount;
if (_contentView != nil && ![_contentView isFlipped])
{
amount = -amount;
}
point.y = clipViewBounds.origin.y - amount;
[_contentView _scrollToPoint: point];
}
/**
* Scrolls the receiver by simply invoking scrollPageUp:
*/
- (void) pageDown: (id)sender
{
[self scrollPageDown: sender];
}
/*
* This code is based on _doScroll:. and still may need some tuning.
*/
- (void) scrollPageDown: (id)sender
{
NSRect clipViewBounds;
NSPoint point;
CGFloat amount;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
}
point = clipViewBounds.origin;
/*
* Take verticalPageScroll into accout, but try to make sure
* that amount is never negativ (ie do not scroll backwards.)
*
* FIXME: It seems _doScroll and scrollWheel: should also take
* care not to do negative scrolling.
*/
amount = clipViewBounds.size.height - _vPageScroll;
amount = (amount < 0) ? 0 : amount;
if (_contentView != nil && ![_contentView isFlipped])
{
amount = -amount;
}
point.y = clipViewBounds.origin.y + amount;
[_contentView _scrollToPoint: point];
}
- (void) _doScroll: (NSScroller*)scroller
{
float floatValue = [scroller floatValue];
NSScrollerPart hitPart = [scroller hitPart];
NSRect clipViewBounds;
NSRect documentRect;
CGFloat amount = 0;
NSPoint point;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
documentRect = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
documentRect = [_contentView documentRect];
}
point = clipViewBounds.origin;
NSDebugLLog (@"NSScrollView", @"_doScroll: float value = %f", floatValue);
/* do nothing if scroller is unknown */
if (scroller != _horizScroller && scroller != _vertScroller)
return;
_knobMoved = NO;
if (hitPart == NSScrollerKnob || hitPart == NSScrollerKnobSlot)
_knobMoved = YES;
else
{
if (hitPart == NSScrollerIncrementLine)
{
if (scroller == _horizScroller)
amount = _hLineScroll;
else
amount = _vLineScroll;
}
else if (hitPart == NSScrollerDecrementLine)
{
if (scroller == _horizScroller)
amount = -_hLineScroll;
else
amount = -_vLineScroll;
}
else if (hitPart == NSScrollerIncrementPage)
{
if (scroller == _horizScroller)
amount = clipViewBounds.size.width - _hPageScroll;
else
amount = clipViewBounds.size.height - _vPageScroll;
}
else if (hitPart == NSScrollerDecrementPage)
{
if (scroller == _horizScroller)
amount = _hPageScroll - clipViewBounds.size.width;
else
amount = _vPageScroll - clipViewBounds.size.height;
}
else
{
return;
}
}
if (!_knobMoved) /* button scrolling */
{
if (scroller == _horizScroller)
{
point.x = clipViewBounds.origin.x + amount;
}
else
{
if (_contentView != nil && ![_contentView isFlipped])
{
/* If view is flipped reverse the scroll direction */
amount = -amount;
}
NSDebugLLog (@"NSScrollView",
@"increment/decrement: amount = %f, flipped = %d",
amount, _contentView ? [_contentView isFlipped] : 0);
point.y = clipViewBounds.origin.y + amount;
}
}
else /* knob scolling */
{
if (scroller == _horizScroller)
{
point.x = floatValue * (documentRect.size.width
- clipViewBounds.size.width);
point.x += documentRect.origin.x;
}
else
{
if (_contentView != nil && ![_contentView isFlipped])
floatValue = 1 - floatValue;
point.y = floatValue * (documentRect.size.height
- clipViewBounds.size.height);
point.y += documentRect.origin.y;
}
}
/* scrollToPoint will call reflectScrollerClipView, and that will
* update scrollers, rulers and headers */
[_contentView _scrollToPoint: point];
}
- (void) scrollToBeginningOfDocument: (id)sender
{
NSRect clipViewBounds, documentRect;
NSPoint point;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
documentRect = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
documentRect = [_contentView documentRect];
}
point = documentRect.origin;
if (_contentView != nil && ![_contentView isFlipped])
{
point.y = NSMaxY(documentRect) - NSHeight(clipViewBounds);
if (point.y < 0)
point.y = 0;
}
[_contentView _scrollToPoint: point];
}
- (void) scrollToEndOfDocument: (id)sender
{
NSRect clipViewBounds, documentRect;
NSPoint point;
if (_contentView == nil)
{
clipViewBounds = NSZeroRect;
documentRect = NSZeroRect;
}
else
{
clipViewBounds = [_contentView bounds];
documentRect = [_contentView documentRect];
}
point = documentRect.origin;
if (_contentView == nil || [_contentView isFlipped])
{
point.y = NSMaxY(documentRect) - NSHeight(clipViewBounds);
if (point.y < 0)
point.y = 0;
}
[_contentView _scrollToPoint: point];
}
//
// This method is here purely for nib compatibility. This is the action
// connected to by NSScrollers in IB when building a scrollview.
//
- (void) _doScroller: (NSScroller *)scroller
{
[self _doScroll: scroller];
}
- (void) reflectScrolledClipView: (NSClipView *)aClipView
{
NSRect documentFrame = NSZeroRect;
NSRect clipViewBounds = NSZeroRect;
float floatValue;
CGFloat knobProportion;
id documentView;
if (aClipView != _contentView)
{
return;
}
NSDebugLLog (@"NSScrollView", @"reflectScrolledClipView:");
if (_contentView)
{
clipViewBounds = [_contentView bounds];
}
if ((documentView = [_contentView documentView]))
{
documentFrame = [documentView frame];
}
// FIXME: Should we just hide the scroll bar or remove it?
if ((_autohidesScrollers)
&& (documentFrame.size.height > clipViewBounds.size.height))
{
[self setHasVerticalScroller: YES];
}
if (_hasVertScroller)
{
if (documentFrame.size.height <= clipViewBounds.size.height)
{
if (_autohidesScrollers)
{
[self setHasVerticalScroller: NO];
}
else
{
[_vertScroller setEnabled: NO];
}
}
else
{
[_vertScroller setEnabled: YES];
knobProportion = clipViewBounds.size.height
/ documentFrame.size.height;
floatValue = (clipViewBounds.origin.y - documentFrame.origin.y)
/ (documentFrame.size.height - clipViewBounds.size.height);
if (![_contentView isFlipped])
{
floatValue = 1 - floatValue;
}
[_vertScroller setFloatValue: floatValue
knobProportion: knobProportion];
}
}
if ((_autohidesScrollers)
&& (documentFrame.size.width > clipViewBounds.size.width))
{
[self setHasHorizontalScroller: YES];
}
if (_hasHorizScroller)
{
if (documentFrame.size.width <= clipViewBounds.size.width)
{
if (_autohidesScrollers)
{
[self setHasHorizontalScroller: NO];
}
else
{
[_horizScroller setEnabled: NO];
}
}
else
{
[_horizScroller setEnabled: YES];
knobProportion = clipViewBounds.size.width
/ documentFrame.size.width;
floatValue = (clipViewBounds.origin.x - documentFrame.origin.x)
/ (documentFrame.size.width - clipViewBounds.size.width);
[_horizScroller setFloatValue: floatValue
knobProportion: knobProportion];
}
}
if (_hasHeaderView)
{
NSPoint headerClipViewOrigin;
headerClipViewOrigin = [_headerClipView bounds].origin;
/* If needed, scroll the headerview too. */
if (headerClipViewOrigin.x != clipViewBounds.origin.x)
{
headerClipViewOrigin.x = clipViewBounds.origin.x;
[_headerClipView scrollToPoint: headerClipViewOrigin];
}
}
if (_rulersVisible == YES)
{
if (_hasHorizRuler)
{
[_horizRuler setNeedsDisplay: YES];
}
if (_hasVertRuler)
{
[_vertRuler setNeedsDisplay: YES];
}
}
}
- (void) setHorizontalRulerView: (NSRulerView *)aRulerView
{
if (_rulersVisible && _horizRuler != nil)
{
[_horizRuler removeFromSuperview];
}
ASSIGN(_horizRuler, aRulerView);
if (_horizRuler == nil)
{
_hasHorizRuler = NO;
}
else if (_rulersVisible)
{
[self addSubview:_horizRuler];
}
if (_rulersVisible)
{
[self tile];
}
}
- (void) setHasHorizontalRuler: (BOOL)flag
{
if (_hasHorizRuler == flag)
return;
_hasHorizRuler = flag;
if (_hasHorizRuler && _horizRuler == nil)
{
_horizRuler = [[object_getClass(self) rulerViewClass] alloc];
_horizRuler = [_horizRuler initWithScrollView: self
orientation: NSHorizontalRuler];
}
if (_rulersVisible)
{
if (_hasHorizRuler)
{
[self addSubview: _horizRuler];
}
else
{
[_horizRuler removeFromSuperview];
}
[self tile];
}
}
- (void) setVerticalRulerView: (NSRulerView *)aRulerView
{
if (_rulersVisible && _vertRuler != nil)
{
[_vertRuler removeFromSuperview];
}
ASSIGN(_vertRuler, aRulerView);
if (_vertRuler == nil)
{
_hasVertRuler = NO;
}
else if (_rulersVisible)
{
[self addSubview:_vertRuler];
}
if (_rulersVisible)
{
[self tile];
}
}
- (void) setHasVerticalRuler: (BOOL)flag
{
if (_hasVertRuler == flag)
return;
_hasVertRuler = flag;
if (_hasVertRuler && _vertRuler == nil)
{
_vertRuler = [[object_getClass(self) rulerViewClass] alloc];
_vertRuler = [_vertRuler initWithScrollView: self
orientation: NSVerticalRuler];
}
if (_rulersVisible)
{
if (_hasVertRuler)
{
[self addSubview: _vertRuler];
}
else
{
[_vertRuler removeFromSuperview];
}
[self tile];
}
}
- (void) setRulersVisible: (BOOL)flag
{
if (_rulersVisible == flag)
return;
_rulersVisible = flag;
if (flag)
{
if (_hasVertRuler)
[self addSubview: _vertRuler];
if (_hasHorizRuler)
[self addSubview: _horizRuler];
}
else
{
if (_hasVertRuler)
[_vertRuler removeFromSuperview];
if (_hasHorizRuler)
[_horizRuler removeFromSuperview];
}
[self tile];
}
- (void) setFrame: (NSRect)rect
{
[super setFrame: rect];
[self tile];
}
- (void) setFrameSize: (NSSize)size
{
[super setFrameSize: size];
[self tile];
}
static NSRectEdge
GSOppositeEdge(NSRectEdge edge)
{
return (edge == NSMinXEdge) ? NSMaxXEdge : NSMinXEdge;
}
- (void) tile
{
NSRect headerRect, contentRect;
NSSize border = [[GSTheme theme] sizeForBorderType: _borderType];
NSRectEdge bottomEdge, topEdge;
CGFloat headerViewHeight = 0;
NSRectEdge verticalScrollerEdge = NSMinXEdge;
NSInterfaceStyle style;
CGFloat innerBorderWidth = [[NSUserDefaults standardUserDefaults]
boolForKey: @"GSScrollViewNoInnerBorder"] ? 0.0 : 1.0;
const BOOL useBottomCorner = [[GSTheme theme] scrollViewUseBottomCorner];
const BOOL overlapBorders = [[GSTheme theme] scrollViewScrollersOverlapBorders];
style = NSInterfaceStyleForKey(@"NSScrollViewInterfaceStyle", nil);
if (style == NSMacintoshInterfaceStyle
|| style == NSWindows95InterfaceStyle)
{
verticalScrollerEdge = NSMaxXEdge;
}
/* Determine edge positions. */
if ([self isFlipped])
{
topEdge = NSMinYEdge;
bottomEdge = NSMaxYEdge;
}
else
{
topEdge = NSMaxYEdge;
bottomEdge = NSMinYEdge;
}
/* Prepare the contentRect by insetting the borders. */
contentRect = _bounds;
if (!overlapBorders)
contentRect = NSInsetRect(contentRect, border.width, border.height);
if (contentRect.size.width < 0 || contentRect.size.height < 0)
{
/* FIXME ... should we do something else when given
* too small a size to tile? */
return;
}
[self _synchronizeHeaderAndCornerView];
if (overlapBorders)
{
if (_borderType != NSNoBorder)
{
if (!(_hasHeaderView || _hasCornerView))
{
// Inset 1px on the top
NSDivideRect(contentRect, NULL, &contentRect, 1, topEdge);
}
if (!_hasVertScroller)
{
// Inset 1px on the edge where the vertical scroller would be
NSDivideRect(contentRect, NULL, &contentRect, 1, verticalScrollerEdge);
}
if (!_hasHorizScroller)
{
NSDivideRect(contentRect, NULL, &contentRect, 1, bottomEdge);
}
// The vertical edge without a scroller
{
NSDivideRect(contentRect, NULL, &contentRect, 1,
GSOppositeEdge(verticalScrollerEdge));
}
}
}
/* First, allocate vertical space for the headerView / cornerView
(but - NB - the headerView needs to be placed above the clipview
later on, we can't place it now). */
if (_hasHeaderView == YES)
{
headerViewHeight = [[_headerClipView documentView] frame].size.height;
}
if (_hasCornerView == YES)
{
if (headerViewHeight == 0)
{
headerViewHeight = [_cornerView frame].size.height;
}
}
/* Remove the vertical slice used by the header/corner view. Save
the height and y position of headerRect for later reuse. */
NSDivideRect (contentRect, &headerRect, &contentRect, headerViewHeight,
topEdge);
/* Ok - now go on with drawing the actual scrollview in the
remaining space. Just consider contentRect to be the area in
which we draw, ignoring header/corner view. */
/* Prepare the vertical scroller. */
if (_hasVertScroller)
{
NSRect vertScrollerRect;
NSDivideRect (contentRect, &vertScrollerRect, &contentRect,
scrollerWidth, verticalScrollerEdge);
/* If the theme requests it, leave a square gap in the bottom-
* left (or bottom-right) corner where the horizontal and vertical
* scrollers meet. */
if (_hasHorizScroller && !useBottomCorner)
{
NSDivideRect (vertScrollerRect, NULL, &vertScrollerRect,
scrollerWidth, bottomEdge);
}
/** Vertically expand the scroller by 1pt on each end */
if (overlapBorders)
{
vertScrollerRect.origin.y -= 1;
vertScrollerRect.size.height += 2;
}
[_vertScroller setFrame: vertScrollerRect];
/* Substract 1 for the line that separates the vertical scroller
* from the clip view (and eventually the horizontal scroller),
* unless the GSScrollViewNoInnerBorder default is set. */
NSDivideRect (contentRect, NULL, &contentRect, innerBorderWidth, verticalScrollerEdge);
}
/* Prepare the horizontal scroller. */
if (_hasHorizScroller)
{
NSRect horizScrollerRect;
NSDivideRect (contentRect, &horizScrollerRect, &contentRect,
scrollerWidth, bottomEdge);
/** Horizontall expand the scroller by 1pt on each end */
if (overlapBorders)
{
horizScrollerRect.origin.x -= 1;
horizScrollerRect.size.width += 2;
}
[_horizScroller setFrame: horizScrollerRect];
/* Substract 1 for the width for the line that separates the
* horizontal scroller from the clip view,
* unless the GSScrollViewNoInnerBorder default is set. */
NSDivideRect (contentRect, NULL, &contentRect, innerBorderWidth, bottomEdge);
}
/* Now place and size the header view to be exactly above the
resulting clipview. */
if (_hasHeaderView)
{
NSRect rect = headerRect;
rect.origin.x = contentRect.origin.x;
rect.size.width = contentRect.size.width;
[_headerClipView setFrame: rect];
}
/* Now place the corner view. */
if (_hasCornerView)
{
NSPoint p = headerRect.origin;
if (verticalScrollerEdge == NSMaxXEdge)
{
p.x += contentRect.size.width;
}
[_cornerView setFrameOrigin: p];
}
/* Now place the rulers. */
if (_rulersVisible)
{
if (_hasHorizRuler)
{
NSRect horizRulerRect;
NSDivideRect (contentRect, &horizRulerRect, &contentRect,
[_horizRuler requiredThickness], topEdge);
[_horizRuler setFrame: horizRulerRect];
}
if (_hasVertRuler)
{
NSRect vertRulerRect;
NSDivideRect (contentRect, &vertRulerRect, &contentRect,
[_vertRuler requiredThickness], NSMinXEdge);
[_vertRuler setFrame: vertRulerRect];
}
}
[_contentView setFrame: contentRect];
[self setNeedsDisplay: YES];
}
- (void) drawRect: (NSRect)rect
{
[[GSTheme theme] drawScrollViewRect: rect
inView: self];
}
- (NSRect) documentVisibleRect
{
return [_contentView documentVisibleRect];
}
- (void) setBackgroundColor: (NSColor*)aColor
{
[_contentView setBackgroundColor: aColor];
}
- (NSColor*) backgroundColor
{
return [_contentView backgroundColor];
}
- (void) setDrawsBackground: (BOOL)flag
{
[_contentView setDrawsBackground: flag];
if ((flag == NO) &&
[_contentView respondsToSelector: @selector(setCopiesOnScroll:)])
[_contentView setCopiesOnScroll: NO];
}
- (BOOL) drawsBackground
{
return [_contentView drawsBackground];
}
- (void) setBorderType: (NSBorderType)borderType
{
_borderType = borderType;
[self tile];
}
- (void) setDocumentView: (NSView *)aView
{
[_contentView setDocumentView: aView];
if (_contentView && ![_contentView isFlipped])
{
[_vertScroller setFloatValue: 1];
}
[self tile];
}
- (void) resizeSubviewsWithOldSize: (NSSize)oldSize
{
[super resizeSubviewsWithOldSize: oldSize];
[self tile];
}
- (id) documentView
{
return [_contentView documentView];
}
- (NSCursor*) documentCursor
{
return [_contentView documentCursor];
}
- (void) setDocumentCursor: (NSCursor*)aCursor
{
[_contentView setDocumentCursor: aCursor];
}
- (BOOL) isOpaque
{
// FIXME: Only needs to be NO in a corner case,
// when [[GSTheme theme] scrollViewUseBottomCorner] is NO
// and the theme tile for the bottom corner is transparent.
// So maybe cache the value of
// [[GSTheme theme] scrollViewUseBottomCorner] and check it here.
return NO;
}
- (NSBorderType) borderType
{
return _borderType;
}
/** <p>Returns whether the NSScrollView has a horizontal ruler</p>
<p>See Also: -setHasHorizontalRuler:</p>
*/
- (BOOL) hasHorizontalRuler
{
return _hasHorizRuler;
}
/** <p>Returns whether the NSScrollView has a horizontal scroller</p>
<p>See Also: -setHasHorizontalScroller:</p>
*/
- (BOOL) hasHorizontalScroller
{
return _hasHorizScroller;
}
/** <p>Returns whether the NSScrollView has a vertical ruler</p>
<p>See Also: -setHasVerticalRuler:</p>
*/
- (BOOL) hasVerticalRuler
{
return _hasVertRuler;
}
/** <p>Returns whether the NSScrollView has a vertical scroller</p>
<p>See Also: -setHasVerticalScroller:</p>
*/
- (BOOL) hasVerticalScroller
{
return _hasVertScroller;
}
/**<p>Returns the size of the NSScrollView's content view</p>
*/
- (NSSize) contentSize
{
return [_contentView bounds].size;
}
- (NSClipView *) contentView
{
return _contentView;
}
- (NSRulerView *) horizontalRulerView
{
return _horizRuler;
}
- (NSRulerView *) verticalRulerView
{
return _vertRuler;
}
- (BOOL) rulersVisible
{
return _rulersVisible;
}
- (void) setLineScroll: (CGFloat)aFloat
{
_hLineScroll = aFloat;
_vLineScroll = aFloat;
}
- (void) setHorizontalLineScroll: (CGFloat)aFloat
{
_hLineScroll = aFloat;
}
- (void) setVerticalLineScroll: (CGFloat)aFloat
{
_vLineScroll = aFloat;
}
- (CGFloat) lineScroll
{
if (_hLineScroll != _vLineScroll)
[NSException raise: NSInternalInconsistencyException
format: @"horizontal and vertical values not same"];
return _vLineScroll;
}
- (CGFloat) horizontalLineScroll
{
return _hLineScroll;
}
- (CGFloat) verticalLineScroll
{
return _vLineScroll;
}
- (void) setPageScroll: (CGFloat)aFloat
{
_hPageScroll = aFloat;
_vPageScroll = aFloat;
}
- (void) setHorizontalPageScroll: (CGFloat)aFloat
{
_hPageScroll = aFloat;
}
- (void) setVerticalPageScroll: (CGFloat)aFloat
{
_vPageScroll = aFloat;
}
- (CGFloat) pageScroll
{
if (_hPageScroll != _vPageScroll)
[NSException raise: NSInternalInconsistencyException
format: @"horizontal and vertical values not same"];
return _vPageScroll;
}
- (CGFloat) horizontalPageScroll
{
return _hPageScroll;
}
- (CGFloat) verticalPageScroll
{
return _vPageScroll;
}
- (void) setScrollsDynamically: (BOOL)flag
{
// FIXME: This should change the behaviour of the scrollers
_scrollsDynamically = flag;
}
- (BOOL) scrollsDynamically
{
return _scrollsDynamically;
}
- (NSScroller*) horizontalScroller
{
return _horizScroller;
}
- (NSScroller*) verticalScroller
{
return _vertScroller;
}
/*
* NSCoding protocol
*/
- (void) encodeWithCoder: (NSCoder*)aCoder
{
[super encodeWithCoder: aCoder];
if ([aCoder allowsKeyedCoding])
{
unsigned long flags = 0;
[aCoder encodeObject: _horizScroller forKey: @"NSHScroller"];
[aCoder encodeObject: _vertScroller forKey: @"NSVScroller"];
[aCoder encodeObject: _contentView forKey: @"NSContentView"];
// only encode this, if it's not null...
if (_headerClipView != nil)
{
[aCoder encodeObject: _headerClipView forKey: @"NSHeaderClipView"];
}
flags = _borderType;
if (_hasVertScroller)
flags |= 16;
if (_hasHorizScroller)
flags |= 32;
if (_autohidesScrollers)
flags |= 512;
[aCoder encodeInt: flags forKey: @"NSsFlags"];
}
else
{
[aCoder encodeObject: _contentView];
// Was int, we need to stay compatible
[aCoder encodeValueOfObjCType: @encode(NSInteger) at: &_borderType];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_scrollsDynamically];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_rulersVisible];
[aCoder encodeValueOfObjCType: @encode(float) at: &_hLineScroll];
[aCoder encodeValueOfObjCType: @encode(float) at: &_hPageScroll];
[aCoder encodeValueOfObjCType: @encode(float) at: &_vLineScroll];
[aCoder encodeValueOfObjCType: @encode(float) at: &_vPageScroll];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHorizScroller];
if (_hasHorizScroller)
[aCoder encodeObject: _horizScroller];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasVertScroller];
if (_hasVertScroller)
[aCoder encodeObject: _vertScroller];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHorizRuler];
if (_hasHorizRuler)
[aCoder encodeObject: _horizRuler];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasVertRuler];
if (_hasVertRuler)
[aCoder encodeObject: _vertRuler];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHeaderView];
if (_hasHeaderView)
[aCoder encodeObject: _headerClipView];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasCornerView];
/* We do not need to encode headerview, cornerview stuff */
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [super initWithCoder: aDecoder];
if (!self)
return nil;
if ([aDecoder allowsKeyedCoding])
{
NSScroller *hScroller = [aDecoder decodeObjectForKey: @"NSHScroller"];
NSScroller *vScroller = [aDecoder decodeObjectForKey: @"NSVScroller"];
NSClipView *content = [aDecoder decodeObjectForKey: @"NSContentView"];
NSView *docView = [content documentView];
BOOL post_frame = [docView postsFrameChangedNotifications];
BOOL post_bound = [docView postsBoundsChangedNotifications];
[docView setPostsFrameChangedNotifications: NO];
[docView setPostsBoundsChangedNotifications: NO];
_hLineScroll = 10;
_hPageScroll = 10;
_vLineScroll = 10;
_vPageScroll = 10;
_scrollsDynamically = YES;
/* _autohidesScroller, _rulersVisible, _hasHorizRuler and _hasVertRuler
implicitly set to NO */
if ([aDecoder containsValueForKey: @"NSsFlags"])
{
int flags = [aDecoder decodeInt32ForKey: @"NSsFlags"];
_borderType = flags & 3;
_hasVertScroller = (flags & 16) == 16;
_hasHorizScroller = (flags & 32) == 32;
_autohidesScrollers = (flags & 512) == 512;
}
/* FIXME: This should only happen when we load a Mac NIB file.
And as far as I can tell tile is handling this correctly.
if (vScroller != nil && _hasVertScroller && content != nil)
{
// Move the content view since it is not moved when we retile.
NSRect frame = [content frame];
float w = [vScroller frame].size.width;
//
// Slide the content view over, since on Mac OS X the scroller is on the
// right, the content view is not properly positioned since our scroller
// is on the left.
//
frame.origin.x += w;
[content setFrame: frame];
}
*/
if (hScroller != nil && _hasHorizScroller)
{
[self setHorizontalScroller: hScroller];
[hScroller setHidden: NO];
}
if (vScroller != nil && _hasVertScroller)
{
[self setVerticalScroller: vScroller];
[vScroller setHidden: NO];
}
if ([aDecoder containsValueForKey: @"NSHeaderClipView"])
{
_hasHeaderView = YES;
_headerClipView = [aDecoder decodeObjectForKey: @"NSHeaderClipView"];
}
// set the document view into the content.
[self setContentView: content];
[self tile];
// Reenable notification sending.
[docView setPostsFrameChangedNotifications: post_frame];
[docView setPostsBoundsChangedNotifications: post_bound];
}
else
{
int version = [aDecoder versionForClassName: @"NSScrollView"];
NSDebugLLog(@"NSScrollView", @"NSScrollView: start decoding\n");
_contentView = [aDecoder decodeObject];
// Was int, we need to stay compatible
[aDecoder decodeValueOfObjCType: @encode(NSInteger) at: &_borderType];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_scrollsDynamically];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_rulersVisible];
[aDecoder decodeValueOfObjCType: @encode(float) at: &_hLineScroll];
[aDecoder decodeValueOfObjCType: @encode(float) at: &_hPageScroll];
[aDecoder decodeValueOfObjCType: @encode(float) at: &_vLineScroll];
[aDecoder decodeValueOfObjCType: @encode(float) at: &_vPageScroll];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHorizScroller];
if (_hasHorizScroller)
[aDecoder decodeValueOfObjCType: @encode(id) at: &_horizScroller];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasVertScroller];
if (_hasVertScroller)
[aDecoder decodeValueOfObjCType: @encode(id) at: &_vertScroller];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHorizRuler];
if (_hasHorizRuler)
[aDecoder decodeValueOfObjCType: @encode(id) at: &_horizRuler];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasVertRuler];
if (_hasVertRuler)
[aDecoder decodeValueOfObjCType: @encode(id) at: &_vertRuler];
if (version == 2)
{
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHeaderView];
if (_hasHeaderView)
_headerClipView = [aDecoder decodeObject];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasCornerView];
}
else if (version == 1)
{
/* This recreates all the info about headerView, cornerView, etc */
[self setDocumentView: [_contentView documentView]];
}
else
{
NSLog(@"unknown NSScrollView version (%d)", version);
DESTROY(self);
return nil;
}
[self tile];
NSDebugLLog(@"NSScrollView", @"NSScrollView: finish decoding\n");
}
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_themeDidActivate:)
name: GSThemeDidActivateNotification
object: nil];
return self;
}
@end
@implementation NSScrollView (GSPrivate)
/* GNUstep private method */
/* we update both of these at the same time during -tile
so there is no reason in seperating them that'd just add
message passing */
- (void) _synchronizeHeaderAndCornerView
{
BOOL hadHeaderView = _hasHeaderView;
BOOL hadCornerView = _hasCornerView;
NSView *aView = nil;
_hasHeaderView = ([[self documentView]
respondsToSelector: @selector(headerView)]
&& (aView=[(NSTableView *)[self documentView] headerView]));
if (_hasHeaderView == YES)
{
if (hadHeaderView == NO)
{
_headerClipView = [NSClipView new];
[self addSubview: _headerClipView];
RELEASE(_headerClipView);
}
[_headerClipView setDocumentView: aView];
}
else if (hadHeaderView == YES)
{
[self removeSubview: _headerClipView];
}
if (_hasHeaderView == YES &&
_hasVertScroller == YES)
{
aView = nil;
_hasCornerView =
([[self documentView] respondsToSelector: @selector(cornerView)]
&& (aView=[(NSTableView *)[self documentView] cornerView]));
if (aView == _cornerView)
return;
if (_hasCornerView == YES)
{
if (hadCornerView == NO)
{
[self addSubview: aView];
}
else
{
[self replaceSubview: _cornerView with: aView];
}
}
else if (hadCornerView == YES)
{
[self removeSubview: _cornerView];
}
_cornerView = aView;
}
else if (_cornerView != nil)
{
[self removeSubview: _cornerView];
_cornerView = nil;
_hasCornerView = NO;
}
}
- (void) _themeDidActivate: (NSNotification*)notification
{
// N.B. Reload cached [NSScroller scrollerWidth] since the
// new theme may have a different scroller width.
//
// Since scrollerWidth is a static, it will get overwritten
// several times; doesn't matter though.
scrollerWidth = [NSScroller scrollerWidth];
[self tile];
}
@end