/**
NSTabView
The tabular view class
Copyright (C) 1999,2000 Free Software Foundation, Inc.
Author: Michael Hanni
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 or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import
#import
#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;
if (_items == nil)
{
ASSIGN(_items, [NSMutableArray array]);
}
[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];
}
/* TODO (Optimize) - just mark the tabs rect as needing redisplay */
[self setNeedsDisplay: YES];
}
- (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 result = [[GSTheme theme] tabViewContentRectForBounds: _bounds
tabViewType: [self tabViewType]
tabView: self];
return result;
}
// 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"]);
[_items makeObjectsPerformSelector: @selector(_setTabView:) withObject: self];
}
else
{
ASSIGN(_items, [NSMutableArray array]);
}
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 archive 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