libs-gui/Source/NSTabView.m
ericwa c673a06970 * Source/NSTabView.m (-initWithCoder:): Make non-keyed archives
call -selectTabViewItem: to select the current tab at unarchiving
	time (keyed archives were already doing this.)

	Comment: "N.B.: As a side effect, this discards the subview frame
	and sets it to [self contentRect].

	This is desirable because the subview frame will be different
	depending on whether the arcive is from Cocoa or GNUstep,
	and which GNUstep theme was active at save time.

	However, it does mean that the tab view contents should be
	prepared to resize slightly."

	The immediate need for this is tab views were changed to use
	flipped coordinates, so the subview frames stored in existing
	archives are now in the wrong coordinate system.

	This is essentially the same idea as NSScrollView calling -tile at the
	end of -initWithCoder: so I hope it makes sense for tab views too.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@37232 72102866-910b-0410-8b05-ffd578937521
2013-10-15 06:17:36 +00:00

781 lines
19 KiB
Objective-C

/** <title>NSTabView</title>
<abstract>The tabular view class</abstract>
Copyright (C) 1999,2000 Free Software Foundation, Inc.
Author: Michael Hanni <mhanni@sprintmail.com>
Date: 1999
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/NSKeyValueCoding.h>
#import <Foundation/NSValue.h>
#import "AppKit/NSColor.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSFont.h"
#import "AppKit/NSFontManager.h"
#import "AppKit/NSForm.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSImage.h"
#import "AppKit/NSKeyValueBinding.h"
#import "AppKit/NSMatrix.h"
#import "AppKit/NSTabView.h"
#import "AppKit/NSTabViewItem.h"
#import "AppKit/NSWindow.h"
#import "GNUstepGUI/GSTheme.h"
#import "GSBindingHelpers.h"
#import "NSViewPrivate.h"
@interface NSTabViewItem (KeyViewLoop)
- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView;
- (NSView *) _lastKeyView;
@end
@implementation NSTabView
/*
* Class methods
*/
+ (void) initialize
{
if (self == [NSTabView class])
{
[self setVersion: 3];
[self exposeBinding: NSSelectedIndexBinding];
[self exposeBinding: NSFontBinding];
[self exposeBinding: NSFontNameBinding];
[self exposeBinding: NSFontSizeBinding];
}
}
- (id) initWithFrame: (NSRect)rect
{
self = [super initWithFrame: rect];
if (self)
{
// setup variables
ASSIGN(_items, [NSMutableArray array]);
ASSIGN(_font, [NSFont systemFontOfSize: 0]);
_selected = nil;
//_truncated_label = NO;
}
return self;
}
- (void) dealloc
{
// Reset the _selected attribute to prevent crash when -dealloc calls
// -setNextKeyView:
_selected = nil;
RELEASE(_items);
RELEASE(_font);
[super dealloc];
}
- (BOOL) isFlipped
{
return YES;
}
// tab management.
- (void) addTabViewItem: (NSTabViewItem*)tabViewItem
{
[self insertTabViewItem: tabViewItem atIndex: [_items count]];
}
- (void) insertTabViewItem: (NSTabViewItem*)tabViewItem
atIndex: (NSInteger)index
{
if (tabViewItem == nil)
return;
[tabViewItem _setTabView: self];
[_items insertObject: tabViewItem atIndex: index];
// If this is the first inserted then select it...
if ([_items count] == 1)
[self selectTabViewItem: tabViewItem];
if ([_delegate respondsToSelector:
@selector(tabViewDidChangeNumberOfTabViewItems:)])
{
[_delegate tabViewDidChangeNumberOfTabViewItems: self];
}
/* TODO (Optimize) - just mark the tabs rect as needing redisplay */
[self setNeedsDisplay: YES];
}
- (void) removeTabViewItem: (NSTabViewItem*)tabViewItem
{
NSUInteger i = [self indexOfTabViewItem: tabViewItem];
if (i == NSNotFound)
return;
RETAIN(tabViewItem);
// Do this BEFORE removing from array...in case it gets released...
[tabViewItem _setTabView: nil];
[_items removeObjectAtIndex: i];
if (tabViewItem == _selected)
{
if ([_items count] == 0)
{
[self selectTabViewItem: nil];
}
else
{
// Select a new tab index...
NSUInteger newIndex = ((i < [_items count]) ? i : (i-1));
[self selectTabViewItem: [_items objectAtIndex: newIndex]];
}
}
RELEASE(tabViewItem);
if ([_delegate respondsToSelector: @selector(tabViewDidChangeNumberOfTabViewItems:)])
{
[_delegate tabViewDidChangeNumberOfTabViewItems: self];
}
}
- (NSInteger) indexOfTabViewItem: (NSTabViewItem*)tabViewItem
{
return [_items indexOfObject: tabViewItem];
}
- (NSInteger) indexOfTabViewItemWithIdentifier: (id)identifier
{
NSUInteger howMany = [_items count];
NSUInteger i;
for (i = 0; i < howMany; i++)
{
id anItem = [_items objectAtIndex: i];
if ([[anItem identifier] isEqual: identifier])
return i;
}
return NSNotFound;
}
- (NSInteger) numberOfTabViewItems
{
return [_items count];
}
- (NSTabViewItem*) tabViewItemAtIndex: (NSInteger)index
{
return [_items objectAtIndex: index];
}
- (NSArray*) tabViewItems
{
return (NSArray*)_items;
}
- (void) selectFirstTabViewItem: (id)sender
{
[self selectTabViewItemAtIndex: 0];
}
- (void) selectLastTabViewItem: (id)sender
{
[self selectTabViewItem: [_items lastObject]];
}
- (void) selectNextTabViewItem: (id)sender
{
NSUInteger selected_item = [self indexOfTabViewItem:_selected];
if (selected_item != NSNotFound)
{
[self selectTabViewItemAtIndex: selected_item + 1];
}
}
- (void) selectPreviousTabViewItem: (id)sender
{
NSUInteger selected_item = [self indexOfTabViewItem:_selected];
if (selected_item != NSNotFound)
{
[self selectTabViewItemAtIndex: selected_item - 1];
}
}
- (NSTabViewItem*) selectedTabViewItem
{
return _selected;
}
- (void) selectTabViewItem: (NSTabViewItem*)tabViewItem
{
BOOL canSelect = YES;
NSView *selectedView = nil;
if ([_delegate respondsToSelector: @selector(tabView:shouldSelectTabViewItem:)])
{
canSelect = [_delegate tabView: self shouldSelectTabViewItem: tabViewItem];
}
if (canSelect)
{
if ([_delegate respondsToSelector: @selector(tabView:willSelectTabViewItem:)])
{
[_delegate tabView: self willSelectTabViewItem: tabViewItem];
}
if (_selected != nil)
{
[_selected _setTabState: NSBackgroundTab];
/* NB: If [_selected view] is nil this does nothing, which
is fine. */
[[_selected view] removeFromSuperview];
}
_selected = tabViewItem;
[_selected _setTabState: NSSelectedTab];
selectedView = [_selected view];
if (selectedView != nil)
{
NSView *firstResponder;
[self addSubview: selectedView];
// FIXME: We should not change this mask
[selectedView setAutoresizingMask:
NSViewWidthSizable | NSViewHeightSizable];
[selectedView setFrame: [self contentRect]];
firstResponder = [_selected initialFirstResponder];
if (firstResponder == nil)
{
firstResponder = [_selected view];
[_selected setInitialFirstResponder: firstResponder];
[firstResponder _setUpKeyViewLoopWithNextKeyView:
_original_nextKeyView];
}
[self setNextKeyView: firstResponder];
[_window makeFirstResponder: firstResponder];
}
/* Will need to redraw tabs and content area. */
[self setNeedsDisplay: YES];
if ([_delegate respondsToSelector:
@selector(tabView:didSelectTabViewItem:)])
{
[_delegate tabView: self didSelectTabViewItem: _selected];
}
}
}
- (void) selectTabViewItemAtIndex: (NSInteger)index
{
if (index < 0 || index >= [_items count])
[self selectTabViewItem: nil];
else
[self selectTabViewItem: [_items objectAtIndex: index]];
}
- (void) selectTabViewItemWithIdentifier: (id)identifier
{
NSInteger index = [self indexOfTabViewItemWithIdentifier: identifier];
[self selectTabViewItemAtIndex: index];
}
- (void) takeSelectedTabViewItemFromSender: (id)sender
{
NSInteger index = -1;
if ([sender respondsToSelector: @selector(indexOfSelectedItem)] == YES)
{
index = [sender indexOfSelectedItem];
}
else if ([sender isKindOfClass: [NSMatrix class]] == YES)
{
NSInteger cols = [sender numberOfColumns];
NSInteger row = [sender selectedRow];
NSInteger col = [sender selectedColumn];
if (row >= 0 && col >= 0)
{
index = row * cols + col;
}
}
[self selectTabViewItemAtIndex: index];
}
- (void) setFont: (NSFont*)font
{
ASSIGN(_font, font);
}
- (NSFont*) font
{
return _font;
}
- (void) setTabViewType: (NSTabViewType)tabViewType
{
_type = tabViewType;
}
- (NSTabViewType) tabViewType
{
return _type;
}
- (void) setDrawsBackground: (BOOL)flag
{
_draws_background = flag;
}
- (BOOL) drawsBackground
{
return _draws_background;
}
- (void) setAllowsTruncatedLabels: (BOOL)allowTruncatedLabels
{
_truncated_label = allowTruncatedLabels;
}
- (BOOL) allowsTruncatedLabels
{
return _truncated_label;
}
- (void) setDelegate: (id)anObject
{
_delegate = anObject;
}
- (id) delegate
{
return _delegate;
}
// content and size
- (NSSize) minimumSize
{
switch (_type)
{
case NSTopTabsBezelBorder:
return NSMakeSize(3, 19);
case NSNoTabsBezelBorder:
return NSMakeSize(3, 3);
case NSNoTabsLineBorder:
return NSMakeSize(2, 2);
case NSBottomTabsBezelBorder:
return NSMakeSize(3, 19);
case NSLeftTabsBezelBorder:
return NSMakeSize(21, 3);
case NSRightTabsBezelBorder:
return NSMakeSize(21, 3);
case NSNoTabsNoBorder:
default:
return NSZeroSize;
}
}
- (NSRect) contentRect
{
NSRect cRect = _bounds;
switch (_type)
{
case NSBottomTabsBezelBorder:
cRect.origin.x += 1;
cRect.origin.y += 1;
cRect.size.width -= 3;
cRect.size.height -= 19;
break;
case NSNoTabsBezelBorder:
cRect.origin.x += 1;
cRect.origin.y += 1;
cRect.size.width -= 3;
cRect.size.height -= 3;
break;
case NSNoTabsLineBorder:
cRect.origin.y += 1;
cRect.origin.x += 1;
cRect.size.width -= 2;
cRect.size.height -= 2;
break;
case NSTopTabsBezelBorder:
cRect.origin.x += 1;
cRect.origin.y += 18;
cRect.size.width -= 3;
cRect.size.height -= 19;
break;
case NSLeftTabsBezelBorder:
cRect.origin.x += 21;
cRect.origin.y += 1;
cRect.size.width -= 21;
cRect.size.height -= 3;
break;
case NSRightTabsBezelBorder:
cRect.origin.x += 1;
cRect.origin.y += 1;
cRect.size.width -= 21;
cRect.size.height -= 3;
break;
case NSNoTabsNoBorder:
default:
break;
}
return cRect;
}
// Drawing.
- (void) drawRect: (NSRect)rect
{
// Make sure some tab is selected
if ((_selected == nil) && ([_items count] > 0))
{
[self selectFirstTabViewItem: nil];
}
[[GSTheme theme] drawTabViewRect: rect
inView: self
withItems: _items
selectedItem: _selected];
}
- (BOOL) isOpaque
{
return NO;
}
// Event handling.
/*
* Find the tab view item containing the NSPoint point. This point
* is expected to be alreay in the coordinate system of the tab view.
*/
- (NSTabViewItem*) tabViewItemAtPoint: (NSPoint)point
{
NSInteger howMany = [_items count];
NSInteger i;
for (i = 0; i < howMany; i++)
{
NSTabViewItem *anItem = [_items objectAtIndex: i];
if (NSPointInRect(point, [anItem _tabRect]))
return anItem;
}
return nil;
}
- (void) mouseDown: (NSEvent *)theEvent
{
NSPoint location = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
NSTabViewItem *anItem = [self tabViewItemAtPoint: location];
if (anItem != nil && ![anItem isEqual: _selected])
{
[self selectTabViewItem: anItem];
GSKeyValueBinding *theBinding = [GSKeyValueBinding getBinding: NSSelectedIndexBinding
forObject: self];
if (theBinding != nil)
[theBinding reverseSetValueFor: NSSelectedIndexBinding];
}
}
- (NSControlSize) controlSize
{
// FIXME
return NSRegularControlSize;
}
/**
* Not implemented.
*/
- (void) setControlSize: (NSControlSize)controlSize
{
// FIXME
}
- (NSControlTint) controlTint
{
// FIXME
return NSDefaultControlTint;
}
/**
* Not implemented.
*/
- (void) setControlTint: (NSControlTint)controlTint
{
// FIXME
}
// Coding.
- (void) encodeWithCoder: (NSCoder*)aCoder
{
[super encodeWithCoder: aCoder];
if ([aCoder allowsKeyedCoding])
{
unsigned int type = _type; // no flags set...
[aCoder encodeBool: [self allowsTruncatedLabels] forKey: @"NSAllowTruncatedLabels"];
[aCoder encodeBool: [self drawsBackground] forKey: @"NSDrawsBackground"];
[aCoder encodeObject: [self font] forKey: @"NSFont"];
[aCoder encodeObject: _items forKey: @"NSTabViewItems"];
[aCoder encodeObject: [self selectedTabViewItem] forKey: @"NSSelectedTabViewItem"];
[aCoder encodeInt: type forKey: @"NSTvFlags"];
}
else
{
NSUInteger selected_item = [self indexOfTabViewItem:_selected];
[aCoder encodeObject: _items];
[aCoder encodeObject: _font];
[aCoder encodeValueOfObjCType: @encode(int) at: &_type];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_draws_background];
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_truncated_label];
[aCoder encodeConditionalObject: _delegate];
[aCoder encodeValueOfObjCType: @encode(NSUInteger) at: &selected_item];
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [super initWithCoder: aDecoder];
if ([aDecoder allowsKeyedCoding])
{
if ([aDecoder containsValueForKey: @"NSAllowTruncatedLabels"])
{
[self setAllowsTruncatedLabels: [aDecoder decodeBoolForKey:
@"NSAllowTruncatedLabels"]];
}
if ([aDecoder containsValueForKey: @"NSDrawsBackground"])
{
[self setDrawsBackground: [aDecoder decodeBoolForKey:
@"NSDrawsBackground"]];
}
if ([aDecoder containsValueForKey: @"NSFont"])
{
[self setFont: [aDecoder decodeObjectForKey: @"NSFont"]];
}
if ([aDecoder containsValueForKey: @"NSTvFlags"])
{
int vFlags = [aDecoder decodeIntForKey: @"NSTvFlags"];
[self setControlTint: ((vFlags & 0x70000000) >> 28)];
[self setControlSize: ((vFlags & 0x0c000000) >> 26)];
[self setTabViewType: (vFlags & 0x00000007)];
}
if ([aDecoder containsValueForKey: @"NSTabViewItems"])
{
ASSIGN(_items, [aDecoder decodeObjectForKey: @"NSTabViewItems"]);
}
if ([aDecoder containsValueForKey: @"NSSelectedTabViewItem"])
{
// N.B.: As a side effect, this discards the subview frame
// and sets it to [self contentRect].
//
// This is desirable because the subview frame will be different
// depending on whether the arcive is from Cocoa or GNUstep,
// and which GNUstep theme was active at save time.
//
// However, it does mean that the tab view contents should be
// prepared to resize slightly.
[self selectTabViewItem: [aDecoder decodeObjectForKey:
@"NSSelectedTabViewItem"]];
}
}
else
{
int version = [aDecoder versionForClassName: @"NSTabView"];
[aDecoder decodeValueOfObjCType: @encode(id) at: &_items];
[aDecoder decodeValueOfObjCType: @encode(id) at: &_font];
[aDecoder decodeValueOfObjCType: @encode(int) at: &_type];
if (version < 2)
{
switch(_type)
{
case 0:
_type = NSTopTabsBezelBorder;
break;
case 5:
_type = NSLeftTabsBezelBorder;
break;
case 1:
_type = NSBottomTabsBezelBorder;
break;
case 6:
_type = NSRightTabsBezelBorder;
break;
case 2:
_type = NSNoTabsBezelBorder;
break;
case 3:
_type = NSNoTabsLineBorder;
break;
case 4:
_type = NSNoTabsNoBorder;
break;
default:
break;
}
}
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_draws_background];
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_truncated_label];
_delegate = [aDecoder decodeObject];
NSUInteger selected_item = NSNotFound;
if (version < 3)
{
int tmp;
[aDecoder decodeValueOfObjCType: @encode(int) at: &tmp];
selected_item = tmp;
}
else
{
[aDecoder decodeValueOfObjCType: @encode(NSUInteger) at: &selected_item];
}
// N.B. Recalculates subview frame; see comment above.
[self selectTabViewItemAtIndex: selected_item];
}
return self;
}
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
if ([aKey isEqual: NSSelectedIndexBinding])
{
[self selectTabViewItemAtIndex: [anObject intValue]];
}
else if ([aKey isEqual: NSFontNameBinding])
{
[self setFont: [[NSFontManager sharedFontManager] convertFont: [self font]
toFace: anObject]];
}
else if ([aKey isEqual: NSFontSizeBinding])
{
[self setFont: [[NSFontManager sharedFontManager] convertFont: [self font]
toSize: [anObject doubleValue]]];
}
else
{
[super setValue: anObject forKey: aKey];
}
}
- (id) valueForKey: (NSString*)aKey
{
if ([aKey isEqual: NSSelectedIndexBinding])
{
return [NSNumber numberWithInt: [self indexOfTabViewItem:
[self selectedTabViewItem]]];
}
else if ([aKey isEqual: NSFontNameBinding])
{
return [[self font] fontName];
}
else if ([aKey isEqual: NSFontSizeBinding])
{
return [NSNumber numberWithDouble: (double)[[self font] pointSize]];
}
else
{
return [super valueForKey: aKey];
}
}
@end
@implementation NSTabViewItem (KeyViewLoop)
- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView
{
[self setInitialFirstResponder: [self view]];
[[self view] _setUpKeyViewLoopWithNextKeyView: nextKeyView];
}
- (NSView *) _lastKeyView
{
NSView *keyView = [self initialFirstResponder];
NSView *itemView = [self view];
NSView *lastKeyView = nil;
NSMutableArray *views = // cycle protection
[[NSMutableArray alloc] initWithCapacity: 1 + [[itemView subviews] count]];
if (keyView == nil && itemView != nil)
{
[self _setUpKeyViewLoopWithNextKeyView: itemView];
}
while ([keyView isDescendantOf: itemView] && ![views containsObject: keyView])
{
[views addObject: keyView];
lastKeyView = keyView;
keyView = [keyView nextKeyView];
}
[views release];
return lastKeyView;
}
@end
@implementation NSTabView (KeyViewLoop)
- (void) _setUpKeyViewLoopWithNextKeyView: (NSView *)nextKeyView
{
[_items makeObjectsPerform: @selector(_setUpKeyViewLoopWithNextKeyView:)
withObject: nextKeyView];
if (_selected)
{
[super setNextKeyView: [_selected initialFirstResponder]];
}
[self setNextKeyView: nextKeyView];
}
- (void) setNextKeyView: (NSView *)nextKeyView
{
_original_nextKeyView = nextKeyView;
if (_selected)
{
[[_selected _lastKeyView] setNextKeyView: nextKeyView];
}
else
{
[super setNextKeyView: nextKeyView];
}
}
@end