mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-24 10:49:12 +00:00
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
1804 lines
44 KiB
Objective-C
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
|
|
|