mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-23 12:09:51 +00:00
560 lines
16 KiB
Objective-C
560 lines
16 KiB
Objective-C
/* Copyright (c) 2006-2007 Christopher J. W. Lloyd
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
#import <AppKit/NSColor.h>
|
|
#import <AppKit/NSFont.h>
|
|
#import <AppKit/NSGraphics.h>
|
|
#import <AppKit/NSGraphicsStyle.h>
|
|
#import <AppKit/NSMatrix.h>
|
|
#import <AppKit/NSPopUpButton.h> // for indexOfSelectedItem definition
|
|
#import <AppKit/NSRaise.h>
|
|
#import <AppKit/NSTabView.h>
|
|
#import <AppKit/NSTabViewItem.h>
|
|
#import <AppKit/NSWindow.h>
|
|
#import <Foundation/NSKeyedArchiver.h>
|
|
|
|
@interface NSTabViewItem (NSTabViewItem_private)
|
|
- (void) setTabView: (NSTabView *) tabView;
|
|
- (void) setTabState: (NSTabState) tabState;
|
|
@end
|
|
|
|
@implementation NSTabView
|
|
|
|
- initWithCoder: (NSCoder *) coder {
|
|
[super initWithCoder: coder];
|
|
|
|
if ([coder allowsKeyedCoding]) {
|
|
NSKeyedUnarchiver *keyed = (NSKeyedUnarchiver *) coder;
|
|
unsigned flags = [keyed decodeIntForKey: @"NSTvFlags"];
|
|
|
|
_items = [[NSMutableArray alloc]
|
|
initWithArray: [keyed decodeObjectForKey: @"NSTabViewItems"]];
|
|
_selectedItem = [keyed decodeObjectForKey: @"NSSelectedTabViewItem"];
|
|
_allowsTruncatedLabels =
|
|
[keyed decodeBoolForKey: @"NSAllowTruncatedLabels"];
|
|
_drawsBackground = [keyed decodeBoolForKey: @"NSDrawsBackground"];
|
|
_controlSize = (flags & 0x18000000) >> 27;
|
|
if (_font == nil)
|
|
_font = [[NSFont boldSystemFontOfSize: 13 - _controlSize * 2]
|
|
retain];
|
|
_type = (flags & 0x7);
|
|
|
|
switch (_type) {
|
|
case NSTopTabsBezelBorder:
|
|
case NSLeftTabsBezelBorder:
|
|
case NSBottomTabsBezelBorder:
|
|
case NSRightTabsBezelBorder: {
|
|
// adjust the layout rectangle
|
|
NSRect frame = [self frame];
|
|
frame.origin.x += 8;
|
|
frame.size.width -= 15;
|
|
switch (_controlSize) {
|
|
case NSRegularControlSize:
|
|
frame.origin.y += 12;
|
|
frame.size.height -= 16;
|
|
break;
|
|
case NSSmallControlSize:
|
|
frame.origin.y += 9;
|
|
frame.size.height -= 13;
|
|
break;
|
|
case NSMiniControlSize:
|
|
frame.origin.y += 8;
|
|
frame.size.height -= 12;
|
|
break;
|
|
}
|
|
[self setFrame: frame];
|
|
}
|
|
|
|
case NSNoTabsBezelBorder:
|
|
case NSNoTabsLineBorder:
|
|
case NSNoTabsNoBorder:
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
[NSException raise: NSInvalidArgumentException
|
|
format: @"-[%@ %s] is not implemented for coder %@",
|
|
[self class], sel_getName(_cmd), coder];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) encodeWithCoder: (NSCoder *) coder {
|
|
NSUnimplementedMethod();
|
|
}
|
|
|
|
- initWithFrame: (NSRect) frame {
|
|
[super initWithFrame: frame];
|
|
_items = [NSMutableArray new];
|
|
_selectedItem = nil;
|
|
// should probably be boldUserFont
|
|
_font = [[NSFont boldSystemFontOfSize: 0] retain];
|
|
_type = NSTopTabsBezelBorder;
|
|
_allowsTruncatedLabels = NO;
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
[_items release];
|
|
_selectedItem = nil;
|
|
[_font release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- delegate {
|
|
return _delegate;
|
|
}
|
|
|
|
- (NSSize) minimumSize {
|
|
switch (_type) {
|
|
case NSTopTabsBezelBorder:
|
|
case NSLeftTabsBezelBorder:
|
|
case NSBottomTabsBezelBorder:
|
|
case NSRightTabsBezelBorder: {
|
|
NSSize finalSize;
|
|
NSInteger i, count = [_items count];
|
|
|
|
if (count == 0)
|
|
break;
|
|
|
|
finalSize =
|
|
[[_items objectAtIndex: 0] sizeOfLabel: _allowsTruncatedLabels];
|
|
for (i = 1; i < count; ++i)
|
|
finalSize.width += [[_items objectAtIndex: i]
|
|
sizeOfLabel: _allowsTruncatedLabels]
|
|
.width;
|
|
|
|
return finalSize;
|
|
}
|
|
|
|
case NSNoTabsBezelBorder:
|
|
case NSNoTabsLineBorder:
|
|
case NSNoTabsNoBorder:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NSMakeSize(0, 0); // correct?
|
|
}
|
|
|
|
- (NSRect) rectForBorder {
|
|
NSRect result = [self bounds];
|
|
|
|
switch (_type) {
|
|
case NSTopTabsBezelBorder:
|
|
case NSLeftTabsBezelBorder:
|
|
case NSBottomTabsBezelBorder:
|
|
case NSRightTabsBezelBorder:
|
|
result.size.height -= 24;
|
|
break;
|
|
|
|
case NSNoTabsBezelBorder:
|
|
case NSNoTabsLineBorder:
|
|
case NSNoTabsNoBorder:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSRect) contentRect {
|
|
NSRect result = [self rectForBorder];
|
|
|
|
switch (_type) {
|
|
case NSTopTabsBezelBorder:
|
|
case NSLeftTabsBezelBorder:
|
|
case NSBottomTabsBezelBorder:
|
|
case NSRightTabsBezelBorder:
|
|
case NSNoTabsBezelBorder:
|
|
return NSInsetRect(result, 2, 2);
|
|
|
|
case NSNoTabsLineBorder:
|
|
return NSInsetRect(result, 1, 1);
|
|
|
|
case NSNoTabsNoBorder:
|
|
default:
|
|
return result;
|
|
}
|
|
}
|
|
|
|
- (NSFont *) font {
|
|
return _font;
|
|
}
|
|
|
|
- (NSTabViewType) tabViewType {
|
|
return _type;
|
|
}
|
|
|
|
- (BOOL) drawsBackground {
|
|
return _drawsBackground;
|
|
}
|
|
|
|
- (BOOL) allowsTruncatedLabels {
|
|
return _allowsTruncatedLabels;
|
|
}
|
|
|
|
- (void) setDelegate: delegate {
|
|
_delegate = delegate;
|
|
}
|
|
|
|
- (void) setFont: (NSFont *) font {
|
|
font = [font retain];
|
|
[_font release];
|
|
_font = font;
|
|
}
|
|
|
|
- (void) setTabViewType: (NSTabViewType) type {
|
|
_type = type;
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) setDrawsBackground: (BOOL) flag {
|
|
_drawsBackground = flag;
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) setAllowsTruncatedLabels: (BOOL) flag {
|
|
_allowsTruncatedLabels = flag;
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (NSInteger) numberOfTabViewItems {
|
|
return [_items count];
|
|
}
|
|
|
|
- (NSArray *) tabViewItems {
|
|
return _items;
|
|
}
|
|
|
|
- (NSTabViewItem *) tabViewItemAtIndex: (NSInteger) index {
|
|
return [_items objectAtIndex: index];
|
|
}
|
|
|
|
- (NSRect) rectForItemLabelAtIndex: (NSUInteger) index {
|
|
NSInteger i, count = [_items count];
|
|
NSPoint base = NSMakePoint(0, [self bounds].size.height - 22);
|
|
NSSize size;
|
|
|
|
base.x += 6;
|
|
for (i = 0; i < count && i < index; i++) {
|
|
size = [[_items objectAtIndex: i] sizeOfLabel: NO];
|
|
|
|
base.x += size.width;
|
|
base.x += 8;
|
|
}
|
|
|
|
size = [[_items objectAtIndex: index] sizeOfLabel: NO];
|
|
|
|
return NSMakeRect(base.x, base.y, size.width, size.height);
|
|
}
|
|
|
|
- (NSTabViewItem *) tabViewItemAtPoint: (NSPoint) point {
|
|
NSInteger i, count = [_items count];
|
|
|
|
for (i = 0; i < count; i++) {
|
|
NSTabViewItem *item = [_items objectAtIndex: i];
|
|
NSRect rect = [self rectForItemLabelAtIndex: i];
|
|
|
|
if (NSMouseInRect(point, rect, [self isFlipped]))
|
|
return item;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSInteger) indexOfTabViewItem: (NSTabViewItem *) item {
|
|
return [_items indexOfObject: item];
|
|
}
|
|
|
|
- (NSInteger) indexOfTabViewItemWithIdentifier: identifier {
|
|
NSInteger i, count = [_items count];
|
|
|
|
for (i = 0; i < count; ++i)
|
|
if ([[(NSTabViewItem *) [_items objectAtIndex: i] identifier]
|
|
isEqual: identifier])
|
|
return i;
|
|
|
|
return NSNotFound;
|
|
}
|
|
|
|
- (void) addTabViewItem: (NSTabViewItem *) item {
|
|
[_items addObject: item];
|
|
if (_selectedItem == nil)
|
|
[self selectTabViewItem: item];
|
|
[item setTabView: self];
|
|
|
|
if ([_delegate respondsToSelector: @selector
|
|
(tabViewDidChangeNumberOfTabViewItems:)])
|
|
[_delegate tabViewDidChangeNumberOfTabViewItems: self];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) removeTabViewItem: (NSTabViewItem *) item {
|
|
NSInteger selectedIndex = [self indexOfTabViewItem: _selectedItem];
|
|
if (item == _selectedItem) {
|
|
NSInteger newIndex = selectedIndex - 1;
|
|
|
|
if (newIndex < 0) {
|
|
newIndex = selectedIndex + 1;
|
|
|
|
if (newIndex >= [self numberOfTabViewItems]) {
|
|
newIndex = NSNotFound;
|
|
}
|
|
}
|
|
|
|
if (newIndex == NSNotFound) {
|
|
[[_selectedItem view] removeFromSuperview];
|
|
_selectedItem = nil;
|
|
} else {
|
|
[self selectTabViewItemAtIndex: newIndex];
|
|
}
|
|
}
|
|
|
|
[_items removeObject: item];
|
|
|
|
if ([_delegate respondsToSelector: @selector
|
|
(tabViewDidChangeNumberOfTabViewItems:)])
|
|
[_delegate tabViewDidChangeNumberOfTabViewItems: self];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (void) insertTabViewItem: (NSTabViewItem *) item atIndex: (NSInteger) index {
|
|
[_items insertObject: item atIndex: index];
|
|
if (_selectedItem == nil)
|
|
[self selectTabViewItem: item];
|
|
[item setTabView: self];
|
|
|
|
if ([_delegate respondsToSelector: @selector
|
|
(tabViewDidChangeNumberOfTabViewItems:)])
|
|
[_delegate tabViewDidChangeNumberOfTabViewItems: self];
|
|
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
|
|
- (NSTabViewItem *) selectedTabViewItem {
|
|
return _selectedItem;
|
|
}
|
|
|
|
- (void) setFrame: (NSRect) frame {
|
|
/* A tab view will autoresize the selected view regardless of whether
|
|
autoresizesSubviews is enabled We do it here because
|
|
resizeSubviewsWithOldSize: won't be called if autoresizesSubviews is off.
|
|
*/
|
|
|
|
[super setFrame: frame];
|
|
if (_selectedItem != nil)
|
|
[[_selectedItem view] setFrame: [self contentRect]];
|
|
}
|
|
|
|
- (void) _itemViewDidChange: (NSTabViewItem *) item {
|
|
if (item == _selectedItem) {
|
|
NSView *itemView = [item view];
|
|
|
|
if (itemView != nil) {
|
|
[self addSubview: itemView];
|
|
[itemView setFrame: [self contentRect]];
|
|
}
|
|
[self setNeedsDisplay: YES];
|
|
}
|
|
}
|
|
|
|
// delegate methods go here
|
|
- (void) selectTabViewItem: (NSTabViewItem *) item {
|
|
if (item != _selectedItem) {
|
|
BOOL selectItem = YES;
|
|
|
|
if ([_delegate respondsToSelector: @selector(tabView:
|
|
shouldSelectTabViewItem:)])
|
|
selectItem = [_delegate tabView: self
|
|
shouldSelectTabViewItem: item];
|
|
|
|
if (selectItem) {
|
|
if ([_delegate respondsToSelector: @selector(tabView:
|
|
willSelectTabViewItem:)])
|
|
[_delegate tabView: self willSelectTabViewItem: item];
|
|
|
|
[[_selectedItem view] removeFromSuperview];
|
|
if ([item view] != nil) {
|
|
[self addSubview: [item view]];
|
|
[[item view] setFrame: [self contentRect]];
|
|
}
|
|
[self setNeedsDisplay: YES];
|
|
_selectedItem = item;
|
|
|
|
if ([item initialFirstResponder])
|
|
[[self window]
|
|
makeFirstResponder: [item initialFirstResponder]];
|
|
|
|
if ([_delegate respondsToSelector: @selector(tabView:
|
|
didSelectTabViewItem:)])
|
|
[_delegate tabView: self didSelectTabViewItem: item];
|
|
}
|
|
// Since we're not opaque, we need to redraw the superview too
|
|
// Shouldn't the view machinery do this automatically?? it might now
|
|
[[self superview] setNeedsDisplay: YES];
|
|
}
|
|
}
|
|
|
|
- (void) selectTabViewItemAtIndex: (NSInteger) index {
|
|
[self selectTabViewItem: [_items objectAtIndex: index]];
|
|
}
|
|
|
|
- (void) selectTabViewItemWithIdentifier: identifier {
|
|
[self selectTabViewItemAtIndex:
|
|
[self indexOfTabViewItemWithIdentifier: identifier]];
|
|
}
|
|
|
|
- (void) selectFirstTabViewItem: sender {
|
|
[self selectTabViewItem: ([_items count] == 0) ? nil
|
|
: [_items objectAtIndex: 0]];
|
|
}
|
|
|
|
- (void) selectLastTabViewItem: sender {
|
|
[self selectTabViewItem: [_items lastObject]];
|
|
}
|
|
|
|
- (void) takeSelectedTabViewItemFromSender: sender {
|
|
if ([sender respondsToSelector: @selector(indexOfSelectedItem)])
|
|
[self selectTabViewItemAtIndex: [sender indexOfSelectedItem]];
|
|
else if ([sender isKindOfClass: [NSMatrix class]])
|
|
[self selectTabViewItemAtIndex:
|
|
[[sender cells] indexOfObject: [sender selectedCell]]];
|
|
}
|
|
|
|
- (NSRect) rectForItemBorderAtIndex: (unsigned) index {
|
|
NSRect result = [self rectForItemLabelAtIndex: index];
|
|
|
|
result = NSInsetRect(result, -4, 0);
|
|
result.origin.y = [self bounds].size.height - 24 - _controlSize;
|
|
result.size.height = 24 - _controlSize;
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void) drawRect: (NSRect) rect {
|
|
NSGraphicsStyle *style = [self graphicsStyle];
|
|
|
|
switch (_type) {
|
|
case NSTopTabsBezelBorder:
|
|
case NSLeftTabsBezelBorder:
|
|
case NSBottomTabsBezelBorder:
|
|
case NSRightTabsBezelBorder: {
|
|
NSInteger i, count = [_items count];
|
|
|
|
for (i = 0; i < count; i++) {
|
|
NSTabViewItem *item = [_items objectAtIndex: i];
|
|
|
|
// defer selected item for overlap
|
|
if (item != _selectedItem) {
|
|
[style drawTabInRect: [self rectForItemBorderAtIndex: i]
|
|
clipRect: rect
|
|
color: [item color]
|
|
selected: NO];
|
|
[item drawLabel: _allowsTruncatedLabels
|
|
inRect: [self rectForItemLabelAtIndex: i]];
|
|
}
|
|
}
|
|
|
|
[style drawTabPaneInRect: [self rectForBorder]];
|
|
|
|
if ((i = [_items indexOfObjectIdenticalTo: _selectedItem]) !=
|
|
NSNotFound) {
|
|
// now do selected item
|
|
i = [_items indexOfObject: _selectedItem];
|
|
[style drawTabInRect: [self rectForItemBorderAtIndex: i]
|
|
clipRect: rect
|
|
color: [_selectedItem color]
|
|
selected: YES];
|
|
[[_selectedItem view] setNeedsDisplay: YES];
|
|
|
|
NSRect labelRect = [self rectForItemLabelAtIndex: i];
|
|
labelRect.origin.y += 2;
|
|
|
|
[_selectedItem drawLabel: _allowsTruncatedLabels inRect: labelRect];
|
|
|
|
if ([[self window] firstResponder] == self)
|
|
NSDottedFrameRect(NSInsetRect(labelRect, -1, 0));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NSNoTabsBezelBorder:
|
|
NSDrawButton([self rectForBorder], rect);
|
|
break;
|
|
|
|
case NSNoTabsLineBorder:
|
|
[[NSColor blackColor] setStroke];
|
|
NSFrameRect([self rectForBorder]);
|
|
break;
|
|
|
|
case NSNoTabsNoBorder:
|
|
if (_drawsBackground) {
|
|
[[NSColor controlColor] setFill];
|
|
NSRectFill([self rectForBorder]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void) mouseDown: (NSEvent *) event {
|
|
NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
|
|
NSTabViewItem *item = [self tabViewItemAtPoint: point];
|
|
|
|
if (item != nil) {
|
|
[_selectedItem setTabState: NSBackgroundTab];
|
|
[self selectTabViewItem: item];
|
|
|
|
// item is "pressed" until mouse up. this correct? docs unclear
|
|
// IB 4.x "selects" immediately (at least visually)
|
|
[_selectedItem setTabState: NSPressedTab];
|
|
|
|
// Since we're not opaque, we need to redraw the superview too
|
|
// Shouldn't the view machinery do this automatically?? it might now
|
|
[[self superview] setNeedsDisplay: YES];
|
|
do {
|
|
event = [[self window]
|
|
nextEventMatchingMask: NSLeftMouseUpMask |
|
|
NSLeftMouseDraggedMask];
|
|
} while ([event type] != NSLeftMouseUp);
|
|
|
|
[_selectedItem setTabState: NSSelectedTab];
|
|
}
|
|
[[self superview] setNeedsDisplay: YES];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSTabView (Bindings)
|
|
|
|
- (NSInteger) _selectedIndex {
|
|
return [self indexOfTabViewItem: _selectedItem];
|
|
}
|
|
- (void) _setSelectedIndex: (NSInteger) selectedIndex {
|
|
[self selectTabViewItemAtIndex: selectedIndex];
|
|
}
|
|
|
|
@end
|