/*
HMTabItemGroupView.m

Author: Makoto Kinoshita

Copyright 2004-2006 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import "HMLabelContentView.h"
#import "HMProgressIndicator.h"
#import "HMTabView.h"
#import "HMTabViewItem.h"
#import "HMTabItemGroupView.h"
#import "HMTabItemView.h"
#import "HMWindow.h"

#import "SRConstants.h"

// Constants
NSString*   HMTabItemViewKey = @"HMTabItemViewKey";

float   HMTabViewThumbnailPaddingTop = 21.0f;
float   HMTabViewThumbnailPaddingLeft = 7.0f;
float   HMTabViewThumbnailPaddingRight = 7.0f;
float   HMTabViewThumbnailPaddingBottom = 8.0f;

NSString*   HMTabItemViewPboardType = @"HMTabItemViewPboardType";

static int  _tabBeginMargin = 14;
static int  _tabEndMargin = 26;
static int  _tabMargin = 2;

@interface HMImageView : NSView
{
    NSImage*    _image;
    float       _alpha;
}
@end
@implementation HMImageView

- (void)dealloc
{
    [_image release];
    
    [super dealloc];
}

- (void)setImage:(NSImage*)image
{
    _image = [image retain];
}

- (void)setAlpha:(float)alpha
{
    _alpha = alpha;
}

- (void)drawRect:(NSRect)rect
{
    NSRect  bounds;
    bounds = [self bounds];
    
    NSRect  srcRect;
    srcRect.origin = NSZeroPoint;
    srcRect.size = [_image size];
    [_image drawInRect:bounds fromRect:srcRect operation:NSCompositeSourceOver fraction:_alpha];
}

@end

@interface HMTabItemGroupView (private)

// Drawing
- (NSRect)_tabItemFrameAtIndex:(int)index;

// Label window
- (NSWindow*)_labelWindow;

// Animation
- (void)_startAnimationWithViewAnimations:(NSArray*)viewAnimations;

@end

@implementation HMTabItemGroupView

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    
    float   time;
    time = [[NSUserDefaults standardUserDefaults] floatForKey:@"HMTabItemFadingTime"];
    if (!time) {
        [[NSUserDefaults standardUserDefaults] setFloat:0.2f forKey:@"HMTabItemFadingTime"];
    }
    
    [pool release];
}

- (id)initWithFrame:(NSRect)frame tabView:(HMTabView*)tabView
{
    self = [super initWithFrame:frame];
    if (!self) {
        return nil;
    }
    
    // Initialize member variables
    _tabView = tabView;
    _tabItemViews = [[NSMutableArray array] retain];
    
    _size = NSRegularControlSize;
    
    _fadeInAnimations = [[NSMutableArray array] retain];
    _fadeOutAnimations = [[NSMutableArray array] retain];
    
    // Register pboard types
    [self registerForDraggedTypes:[NSArray arrayWithObject:HMTabItemViewPboardType]];
    
    return self;
}

- (void)viewDidMoveToWindow
{
    // Register itself as mouse move listener
    id  window;
    window = [self window];
    if ([window respondsToSelector:@selector(addMouseMoveListener:)]) {
        //[window addMouseMoveListener:self];
    }
}

- (void)viewWillMoveToWindow:(NSWindow*)newWindow
{
    // Remove itself
    id  window;
    window = [self window];
    if ([window respondsToSelector:@selector(removeMouseMoveListener:)]) {
        [window removeMouseMoveListener:self];
    }
}

- (void)dealloc
{
    [_tabItemViews release], _tabItemViews = nil;
    
    [_labelWindow release], _labelWindow = nil;
    
    [_fadeInAnimations release], _fadeInAnimations = nil;
    [_fadeOutAnimations release], _fadeOutAnimations = nil;
    
    // Remove itself
    id  window;
    window = [self window];
    if ([window respondsToSelector:@selector(removeMouseMoveListener:)]) {
        [window removeMouseMoveListener:self];
    }
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Tab view item --
//--------------------------------------------------------------//

- (void)_fadeInTabItemView:(HMTabItemView*)tabItemView
{
    // Hide tab item view
    [tabItemView setHidden:YES];
    
    // Decide target frame
    NSRect  targetFrame, startFrame;
    targetFrame = [self _tabItemFrameAtIndex:[_tabItemViews indexOfObject:tabItemView]];
    startFrame.origin.x = targetFrame.origin.x + targetFrame.size.width / 2.0f;
    startFrame.origin.y = targetFrame.origin.y + targetFrame.size.height / 2.0f;
    startFrame.size = NSZeroSize;
    
    // Create image
    NSImage*    image;
    NSRect      rect;
    image = [[NSImage alloc] initWithSize:targetFrame.size];
    [image setFlipped:[self isFlipped]];
    [image setScalesWhenResized:YES];
    [image lockFocus];
    
    rect.origin = NSZeroPoint;
    rect.size = targetFrame.size;
    [tabItemView setFrame:rect];
    [tabItemView drawRect:rect];
    
    [image unlockFocus];
    
    // Create image view
    HMImageView*    imageView;
    imageView = [[HMImageView alloc] initWithFrame:NSZeroRect];
    [imageView setImage:image];
    [image release];
    [self addSubview:imageView];
    [imageView release];
    
    // Cnfigure animation
    NSMutableArray* viewAnimations;
    NSDictionary*   viewAnimation;
    viewAnimations = [NSMutableArray array];
    
    viewAnimation = [NSDictionary dictionaryWithObjectsAndKeys:
            imageView, NSViewAnimationTargetKey, 
            [NSValue valueWithRect:startFrame], NSViewAnimationStartFrameKey, 
            [NSValue valueWithRect:targetFrame], NSViewAnimationEndFrameKey, 
            tabItemView, HMTabItemViewKey, 
            nil];
    
    [viewAnimations addObject:viewAnimation];
    
    // Start animations
    NSAnimation*    animation;
    animation = [[NSViewAnimation alloc] initWithViewAnimations:viewAnimations];
    [animation setDuration:[[NSUserDefaults standardUserDefaults] floatForKey:@"HMTabItemFadingTime"]];
    [animation setFrameRate:0.0f];
    [animation setAnimationBlockingMode:NSAnimationNonblockingThreaded];
    //[animation setAnimationBlockingMode:NSAnimationBlocking];
    [animation setAnimationCurve:NSAnimationEaseIn];
    [animation setDelegate:self];
    [_fadeInAnimations addObject:animation];
    [animation release];
    
    [animation startAnimation];
}

- (void)addTabViewItem:(HMTabViewItem*)tabViewItem
{
    // Create tab item view
    HMTabItemView*  tabItemView;
    tabItemView = [[HMTabItemView alloc] initWithFrame:NSZeroRect tabViewItem:tabViewItem];
    [tabItemView setTabItemGroupView:self];
    [_tabItemViews addObject:tabItemView];
    [self addSubview:tabItemView];
    [tabItemView release];
    
    // Set to tab view item
    [tabViewItem setTabItemView:tabItemView];
    
    // For animation
    if ([_tabItemViews count] > 1 && [_tabView hasThumbnail]) {
        [self _fadeInTabItemView:tabItemView];
    }
    
    // Update tab frames
    [self updateTabFrames];
}

- (void)insertTabViewItem:(HMTabViewItem*)tabViewItem atIndex:(unsigned int)index
{
    // Create tab item view
    HMTabItemView*  tabItemView;
    tabItemView = [[HMTabItemView alloc] initWithFrame:NSZeroRect tabViewItem:tabViewItem];
    [tabItemView setTabItemGroupView:self];
    if (index < [_tabItemViews count]) {
        [_tabItemViews insertObject:tabItemView atIndex:index];
    }
    else {
        [_tabItemViews addObject:tabItemView];
    }
    [self addSubview:tabItemView];
    [tabItemView release];
    
    // Set to tab view item
    [tabViewItem setTabItemView:tabItemView];
    
    // For animation
    if ([_tabItemViews count] > 1 && [_tabView hasThumbnail]) {
        [self _fadeInTabItemView:tabItemView];
    }
    
    // Update tab frames
    [self updateTabFrames];
}

- (void)_fadeOutTabItemView:(HMTabItemView*)tabItemView
{
    // Hide tab item view
    [tabItemView setHidden:YES];
    
    // Decide target frame
    NSRect  targetFrame, startFrame;
    startFrame = [tabItemView frame];
    targetFrame.origin.x = startFrame.origin.x + startFrame.size.width / 2.0f;
    targetFrame.origin.y = startFrame.origin.y + startFrame.size.height / 2.0f;
    targetFrame.size = NSZeroSize;
    
    // Create image
    NSImage*    image;
    NSRect      rect;
    image = [[NSImage alloc] initWithSize:startFrame.size];
    [image setFlipped:[self isFlipped]];
    [image setScalesWhenResized:YES];
    [image lockFocus];
    
    rect.origin = NSZeroPoint;
    rect.size = startFrame.size;
    [tabItemView setFrame:rect];
    [tabItemView drawRect:rect];
    
    [image unlockFocus];
    
    // Create image view
    HMImageView*    imageView;
    imageView = [[HMImageView alloc] initWithFrame:NSZeroRect];
    [imageView setImage:image];
    [image release];
    [self addSubview:imageView];
    [imageView release];
    
    // Cnfigure animation
    NSMutableArray* viewAnimations;
    NSDictionary*   viewAnimation;
    viewAnimations = [NSMutableArray array];
    
    viewAnimation = [NSDictionary dictionaryWithObjectsAndKeys:
            imageView, NSViewAnimationTargetKey, 
            [NSValue valueWithRect:startFrame], NSViewAnimationStartFrameKey, 
            [NSValue valueWithRect:targetFrame], NSViewAnimationEndFrameKey, 
            tabItemView, HMTabItemViewKey, 
            nil];
    
    [viewAnimations addObject:viewAnimation];
    
    // Start animations
    NSAnimation*    animation;
    animation = [[NSViewAnimation alloc] initWithViewAnimations:viewAnimations];
    [animation setDuration:[[NSUserDefaults standardUserDefaults] floatForKey:@"HMTabItemFadingTime"]];
    [animation setFrameRate:0.0f];
    [animation setAnimationBlockingMode:NSAnimationNonblockingThreaded];
    //[animation setAnimationBlockingMode:NSAnimationBlocking];
    [animation setAnimationCurve:NSAnimationEaseIn];
    [animation setDelegate:self];
    [_fadeOutAnimations addObject:animation];
    [animation release];
    
    [animation startAnimation];
}

- (void)removeTabViewItem:(HMTabViewItem*)tabViewItem
{
    // Find tab item view
    NSEnumerator*   enumerator;
    HMTabItemView*  tabItemView;
    enumerator = [_tabItemViews objectEnumerator];
    while (tabItemView = [enumerator nextObject]) {
        if ([tabItemView tabViewItem] == tabViewItem) {
            break;
        }
    }
    
    if (!tabItemView) {
        return;
    }
    
    // For animation
    if ([_tabView hasThumbnail]) {
        [self _fadeOutTabItemView:tabItemView];
    }
    
    // Remove tab item view
    [_tabItemViews removeObject:tabItemView];
    [tabItemView removeFromSuperview];
    
    if (![_tabView hasThumbnail]) {
        // Update tab frames
        [self updateTabFrames];
    }
}

- (int)indexOfCloseButton:(NSButton*)closeButton
{
    int i;
    for (i = 0; i < [_tabItemViews count]; i++) {
        if ([[_tabItemViews objectAtIndex:i] closeButton] == closeButton) {
            return i;
        }
    }
    
    return NSNotFound;
}

- (HMTabViewItem*)draggingTabViewItem
{
    return _draggingItem;
}

//--------------------------------------------------------------//
#pragma mark -- Size --
//--------------------------------------------------------------//

- (NSControlSize)size
{
    return _size;
}

- (void)setSize:(NSControlSize)size
{
    _size = size;
}

- (NSSize)preferredSize
{
    return _preferredSize;
}

- (float)minTabWide
{
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        return 640.0f;
    }
    // For no thumbnail
    else {
        // Switch by size
        switch (_size) {
        case NSRegularControlSize: {
            return 64.0f;
        }
        case NSSmallControlSize: {
            return 64.0f;
        }
        case NSMiniControlSize: {
            return 64.0f;
        }
        }
        
        return 0.0f;
    }
}

- (float)maxTabWide
{
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        return 180.0f;
    }
    // For no thumbnail
    else {
        // Switch by size
        switch (_size) {
        case NSRegularControlSize: {
            return 180.0f;
        }
        case NSSmallControlSize: {
            return 100.0f;
        }
        case NSMiniControlSize: {
            return 80.0f;
        }
        }
        
        return 0.0f;
    }
}

- (float)minTabHigh
{
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        return 80.0f;
    }
    
    // For no thumbnail
    switch (_size) {
        case NSRegularControlSize: {
            return 25.0f;
        }
        case NSSmallControlSize: {
            return 16.0f;
        }
        case NSMiniControlSize: {
            return 14.0f;
        }
    }
    
    return 0.0f;
}

- (float)maxTabHigh
{
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        return 180.0f;
    }
    // For no thumbnail
    else {
        // Same with min value
        return [self minTabHigh];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Mouse operation --
//--------------------------------------------------------------//

- (NSRect)_trackingHandleRect
{
    // Decide tracking rect
    NSRect  bounds, rect;
    bounds = [self bounds];
    
    rect.size.width = 15;
    rect.size.height = 12;
    rect.origin.x = bounds.size.width - rect.size.width;
    rect.origin.y = bounds.size.height - rect.size.height;
    
    return rect;
}

- (void)mouseDown:(NSEvent*)event
{
    // Get mouse location
    NSPoint point;
    point = [self convertPoint:[event locationInWindow] fromView:nil];
    _trackingStartPoint = point;
    
#if 0
    // For handle dragging
    if (NSPointInRect(point, [self _trackingHandleRect])) {
        [self _mouseDown:event];
//        [[_tabView splitView] _trackMouse:event];
        return;
    }
#endif
}

- (void)resetCursorRects
{
    // Discard cursor rects
    [self discardCursorRects];
    
    // Add cursor rects
    [self addCursorRect:[self _trackingHandleRect] cursor:[NSCursor resizeUpDownCursor]];
}

#if 1
- (void)_mouseDown:(NSEvent*)event
{
    // Get split view for dragging
    NSSplitView*    splitView;
    splitView = [_tabView splitView];
    if (!splitView) {
        return;
    }
    
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    // Get mouse location
    NSPoint mouseLoc;
    mouseLoc = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // Get subview for dragging
    NSView* view = nil;
    NSRect  viewFrame;
    if ([[splitView subviews] count] > 1) {
        view = [[splitView subviews] objectAtIndex:1];
    }
    if (!view) {
        return;
    }
    viewFrame = [view frame];
    
    // Track mouse
    if ([self mouse:mouseLoc inRect:[self _trackingHandleRect]]) {
        // Notify to delegate
        id  delegate;
        delegate = [splitView delegate];
        
        if ([delegate respondsToSelector:@selector(splitViewStartDragging:)]) {
            [delegate splitViewStartDragging:splitView];
        }
        
        // Event loop
        while (YES) {
            // Wait next event
            NSEvent*    waitingEvent;
            waitingEvent = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) 
                    untilDate:[NSDate distantFuture] 
                    inMode:NSEventTrackingRunLoopMode 
                    dequeue:YES];
            if (!waitingEvent) {
                break;
            }
            
            // For dragging
            if ([waitingEvent type] == NSLeftMouseDragged) {
                // Calc dragging distant
                NSPoint newMouseLoc;
                int     delta;
                newMouseLoc = [self convertPoint:[waitingEvent locationInWindow] fromView:nil];
                delta = [waitingEvent locationInWindow].y - [event locationInWindow].y;
                
                if (delta != 0) {
                    NSRect  newFrame;
                    newFrame = viewFrame;
                    newFrame.size.height += delta;
                    [view setFrame:newFrame];
                    
                    [splitView adjustSubviews];
                    
                    // Notify to delegate
                    if ([delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)]) {
                        [delegate splitView:splitView resizeSubviewsWithOldSize:NSZeroSize];
                    }
                }
                
                continue;
            }
            
            // For mouse up
            if ([waitingEvent type] == NSLeftMouseUp) {
                break;
            }
        }
        
        // Notify to delegate
        if ([delegate respondsToSelector:@selector(splitViewEndDragging:)]) {
            [delegate splitViewStartDragging:splitView];
        }
    }
    
    [self setNeedsDisplay:YES];
}
#endif

//--------------------------------------------------------------//
#pragma mark -- Dragging --
//--------------------------------------------------------------//

- (void)addDraggedTypes:(NSArray*)pboardTypes
{
    NSMutableArray* registeredTypes;
    registeredTypes = [NSMutableArray arrayWithArray:[self registeredDraggedTypes]];
    
    // Add types
    NSEnumerator*   enumerator;
    NSString*       type;
    enumerator = [pboardTypes objectEnumerator];
    while (type = [enumerator nextObject]) {
        if (![registeredTypes containsObject:type]) {
            [registeredTypes addObject:type];
        }
    }
    
    // Register types
    [self registerForDraggedTypes:registeredTypes];
}

- (void)mouseDragged:(NSEvent*)event
{
    // Get mouse location
    NSPoint point;
    point = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // Decide start dragging or not
    float   dx, dy;
    dx = fabs(point.x-_trackingStartPoint.x);
    dy = fabs(point.y-_trackingStartPoint.y);
    if (dx < 3 && dy < 3) {
        return;
    }
    
#if 0
    // For handle dragging
    if (NSPointInRect(point, [self _trackingHandleRect])) {
        NSLog(@"Handle dragging");
        return;
    }
#endif
    
#if 0
    // Find tag index under mouse
    int     i, index = NSNotFound;
    NSRect  tabFrame;
    for (i = 0; i < [_tabFrames count]; i++) {
        tabFrame = [[_tabFrames objectAtIndex:i] rectValue];
        if (NSPointInRect(point, tabFrame)) {
            index = i;
            break;
        }
    }
    if (index == NSNotFound) {
        return;
    }
    
    // Create drag image
    HMTabViewItem*  tabViewItem;
    NSImage*        image;
    NSImage*        transparentImage;
    NSRect          frame;
    frame = tabFrame;
    tabViewItem = [_tabView tabViewItemAtIndex:index];
    
    image = [[NSImage alloc] initWithSize:frame.size];
    [image autorelease];
    [image lockFocus];
    frame.origin = NSZeroPoint;
    [tabViewItem drawLabelInRect:frame];
    [image unlockFocus];
    
    transparentImage = [[NSImage alloc] initWithSize:frame.size];
    [transparentImage autorelease];
    [transparentImage lockFocus];
    [image dissolveToPoint:NSZeroPoint fraction:0.7f];
    [transparentImage unlockFocus];
    
    // Write index to pasteboard
    NSPasteboard*   pboard;
    pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
    [pboard declareTypes:[NSArray arrayWithObject:HMTabItemViewPboardType] owner:nil];
    
    // Update dragging tab
    _draggingItem = tabViewItem;
    [self setNeedsDisplayInRect:tabFrame];
    
    // Hide close button
    if ([_closeButtons count] > index) {
        [[_closeButtons objectAtIndex:index] setHidden:YES];
    }
    
    // Update label window
    [self updateLabelWindow];
    
    // Start dragging
    [self dragImage:transparentImage 
            at:tabFrame.origin 
            offset:NSZeroSize 
            event:event 
            pasteboard:pboard 
            source:self 
            slideBack:YES];
#endif
}

//--------------------------------------------------------------//
#pragma mark -- Drawing --
//--------------------------------------------------------------//

- (BOOL)isFlipped
{
    return YES;
}

- (NSRect)_tabItemFrameAtIndex:(int)index
{
    // Get tab position
    HMTabViewPosition   tabPosition;
    tabPosition = [_tabView tabViewPosition];
    
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    float   x, y, w, h;
    
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        // Decide tab frames
        switch (tabPosition) {
        case HMTabViewTop:
        case HMTabViewBottom: {
            h = frame.size.height - 6;
            
            w = ceil((h - HMTabViewThumbnailPaddingTop - HMTabViewThumbnailPaddingBottom) / 5 * 8 + 
                    HMTabViewThumbnailPaddingLeft + HMTabViewThumbnailPaddingRight);
            
            x = _tabBeginMargin;
            if (index > 0) {
                x += index * w + (index - 1) * _tabMargin;
            }
            
            if ([self isFlipped]) {
                y = 1;
            }
            else {
                y = 4;
            }
            
            return NSMakeRect(x, y, w, h);
        }
        case HMTabViewLeft:
        case HMTabViewRight: {
            x = 1;
            
            y = _tabBeginMargin;
            if (index > 0) {
                y += index * h + (index - 1) * _tabMargin;
            }
            
            w = frame.size.width - 6;
            
            h = ceil((w - HMTabViewThumbnailPaddingLeft - HMTabViewThumbnailPaddingRight) / 8 * 5 + 
                    HMTabViewThumbnailPaddingTop + HMTabViewThumbnailPaddingBottom);
            
            return NSMakeRect(x, y, w, h);
        }
        }
    }
    else {
#if 0
        // Get min, max wide and high
        float   minWide, maxWide;
        minWide = [self minTabWide];
        maxWide = [self maxTabWide];
        
        // Decide wide for tabs
        float   wide;
        switch (tabPosition) {
        case HMTabViewTop:
        case HMTabViewBottom: {
            wide = frame.size.width - _tabBeginMargin - _tabEndMargin;
            break;
        }
        case HMTabViewLeft:
        case HMTabViewRight: {
            wide = frame.size.height - _tabBeginMargin - _tabEndMargin;
            break;
        }
        }
        
        // When wide is enough to show all max wide tabs
        x = _tabBeginMargin;
        y = 0;
        h = frame.size.height;
        w = ceil(wide / numberOfTabs);
        if (w > maxWide) {
            w = maxWide;
        }
        else if (w < minWide) {
            int number;
            number = (int)(wide / minWide);
            w = ceil(wide / number);
        }
        
        for (i = 0; i < numberOfTabs; i++) {
            NSRect  tabRect;
            tabRect = NSMakeRect(x, y, w, h);
            
            HMTabItemView*  tabItemView;
            tabItemView = [_tabItemViews objectAtIndex:i];
            [tabItemView setFrame:tabRect];
            
            x += w;
        }
#endif
    }
    
    return NSZeroRect;
}

- (void)updateTabFrames
{
    // Get tab position
    HMTabViewPosition   tabPosition;
    tabPosition = [_tabView tabViewPosition];
    
    // Get number of tabs
    int numberOfTabs;
    numberOfTabs = [_tabItemViews count];
    
    int i;
    
    // For thumbnail
    if ([_tabView hasThumbnail]) {
        // Update tab frames
        NSRect  rect;
        for (i = 0; i < numberOfTabs; i++) {
            rect = [self _tabItemFrameAtIndex:i];
            
            HMTabItemView*  tabItemView;
            tabItemView = [_tabItemViews objectAtIndex:i];
            [tabItemView setFrame:rect];
            [tabItemView setNeedsDisplay:YES];
        }
        
        // Decide preferred size
        switch (tabPosition) {
        case HMTabViewTop:
        case HMTabViewBottom: {
            _preferredSize.width = rect.origin.x + rect.size.width + _tabEndMargin;
            _preferredSize.height = rect.size.height;
            
            break;
        }
        case HMTabViewLeft:
        case HMTabViewRight: {
            _preferredSize.width = rect.size.width;
            _preferredSize.height = rect.origin.y + rect.size.height + _tabEndMargin;
            
            break;
        }
        }
    }
    else {
        // Get frame
        NSRect  frame;
        frame = [self frame];
        
        // Get min, max wide and high
        float   minWide, maxWide;
        minWide = [self minTabWide];
        maxWide = [self maxTabWide];
        
        // Decide wide for tabs
        float   wide;
        switch (tabPosition) {
        case HMTabViewTop:
        case HMTabViewBottom: {
            wide = frame.size.width - _tabBeginMargin - _tabEndMargin;
            break;
        }
        case HMTabViewLeft:
        case HMTabViewRight: {
            wide = frame.size.height - _tabBeginMargin - _tabEndMargin;
            break;
        }
        }
        
        float   x, y, w, h;
        
        // When wide is enough to show all max wide tabs
        x = _tabBeginMargin;
        y = 0;
        h = frame.size.height;
        w = ceil(wide / numberOfTabs);
        if (w > maxWide) {
            w = maxWide;
        }
        else if (w < minWide) {
            int number;
            number = (int)(wide / minWide);
            w = ceil(wide / number);
        }
        
        for (i = 0; i < numberOfTabs; i++) {
            NSRect  tabRect;
            tabRect = NSMakeRect(x, y, w, h);
            
            HMTabItemView*  tabItemView;
            tabItemView = [_tabItemViews objectAtIndex:i];
            [tabItemView setFrame:tabRect];
            
            x += w;
        }
    }
    
    // Update itself
    [self setNeedsDisplay:YES];
}

- (void)setFrame:(NSRect)frame
{
    // Get old frame
    NSRect  oldFrame;
    oldFrame = [self frame];
    
    // Invoke super
    [super setFrame:frame];
    
    // Update tab frames
    if (!NSEqualRects(frame, oldFrame)) {
        [self updateTabFrames];
    }
}

- (void)_drawShiira1BackgroundRect:(NSRect)rect
{
    // Get tab position
    HMTabViewPosition   tabPosition;
    tabPosition = [_tabView tabViewPosition];
    
    // x, y, w, h
    float x, y, w, h;
    x = rect.origin.x;
    y = rect.origin.x;
    w = rect.size.width;
    h = rect.size.height;
    
    // Fill background
    [[NSColor colorWithCalibratedWhite:0.0 alpha:0.08f] set];
    NSRectFillUsingOperation(rect, NSCompositeSourceAtop); 
    
    //
    // Draw edge line
    //
    
    // Decide line color
    NSColor*    lineColor;
    lineColor = [HMTabView lineColorWithWindow:[_tabView window]];
    [lineColor set];
    
    // Draw line
    switch (tabPosition) {
    case HMTabViewTop: {
        [NSBezierPath strokeLineFromPoint:NSMakePoint(x, y + h - 0.5f) 
                toPoint:NSMakePoint(x + w, y + h - 0.5f)];
        break;
    }
    case HMTabViewLeft: {
        [NSBezierPath strokeLineFromPoint:NSMakePoint(x + w - 0.5f, y) 
                toPoint:NSMakePoint(x + w - 0.5f, y + h)];
        break;
    }
    case HMTabViewRight: {
        [NSBezierPath strokeLineFromPoint:NSMakePoint(x + 0.5f, y) 
                toPoint:NSMakePoint(x + 0.5f, y + h)];
        break;
    }
    case HMTabViewBottom: {
        [NSBezierPath strokeLineFromPoint:NSMakePoint(x, y + h - 0.5f) 
                toPoint:NSMakePoint(x + w, y + h - 0.5f)];
        break;
    }
    }
    
    //
    // Draw highlight
    //
    
    // Decide highlight color
    NSColor*    highlightColor;
    highlightColor = [HMTabView highlightLineColorWithWindow:[_tabView window]];
    [highlightColor set];
    
    // Draw line
    switch (tabPosition) {
    case HMTabViewTop: {
        [NSBezierPath strokeLineFromPoint:NSMakePoint(x, y + 0.5f) 
                toPoint:NSMakePoint(x + w, y + 0.5f)];
        break;
    }
    }
}

- (void)_drawShiira2BackgroundRect:(NSRect)rect
{
    // Get tab position
    HMTabViewPosition   tabPosition;
    tabPosition = [_tabView tabViewPosition];
    
    // Get bounds
    NSRect  bounds;
    bounds = [self bounds];
    
    // x, y, w, h
    float x, y, w, h;
    x = bounds.origin.x;
    y = bounds.origin.x;
    w = bounds.size.width;
    h = bounds.size.height;
    
    // Fill background
    static NSColor* _backgroundColor = nil;
    if (!_backgroundColor) {
        _backgroundColor = [[NSColor colorWithCalibratedWhite:0.73f alpha:1.0f] retain];
    }
    [_backgroundColor set];
    NSRectFill(rect);
    
    //
    // Draw tab bar
    //
    
    // Get tab bar image
    NSImage*    tabBarImage;
    tabBarImage = [NSImage imageNamed:@"tabBarBack"];
    [tabBarImage setFlipped:[self isFlipped]];
    
    // Draw tab bar
    NSRect  srcRect, destRect;
    srcRect.origin = NSZeroPoint;
    srcRect.size = [tabBarImage size];
    
    switch (tabPosition) {
    case HMTabViewTop: {
        destRect.origin.x = x;
        destRect.origin.y = y + h - srcRect.size.height;
        destRect.size.width = w;
        destRect.size.height = srcRect.size.height;
        [tabBarImage drawInRect:destRect fromRect:srcRect operation:NSCompositeSourceOver fraction:1.0f];
        break;
    }
    }
}

- (void)drawRect:(NSRect)rect
{
    // Draw background
    if (![_tabView hasThumbnail]) {
        [self _drawShiira2BackgroundRect:rect];
    }
    
#if 0
    // Draw handle
    static NSImage* _verticalHandleImage = nil;
    if (!_verticalHandleImage) {
        _verticalHandleImage = [[NSImage imageNamed:@"tabVerticalHandle"] retain];
    }
    
    // Set flip
    if ([_verticalHandleImage isFlipped] != [self isFlipped]) {
        [_verticalHandleImage setFlipped:[self isFlipped]];
    }
    
    // Draw image
    NSRect  bounds, srcRect, destRect;
    bounds = [self bounds];
    srcRect.origin = NSZeroPoint;
    srcRect.size = [_verticalHandleImage size];
    destRect.origin.x = bounds.size.width - srcRect.size.width;
    destRect.origin.y = bounds.size.height - srcRect.size.height - 2.0f;
    destRect.size = srcRect.size;
    [_verticalHandleImage drawInRect:destRect fromRect:srcRect operation:NSCompositeSourceOver fraction:1.0f];
#endif
}

//--------------------------------------------------------------//
#pragma mark -- Label window --
//--------------------------------------------------------------//

- (NSWindow*)_labelWindow
{
    if (_labelWindow) {
        return _labelWindow;
    }
    
    // Create label window
    _labelWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100) 
            styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
    [_labelWindow setLevel:NSFloatingWindowLevel];
    [_labelWindow setOpaque:NO];
    
    // Set label content view
    NSRect              contntFrame;
    HMLabelContentView* contentView;
    contntFrame = [_labelWindow frame];
    contntFrame.origin = NSZeroPoint;
    contentView = [[HMLabelContentView alloc] initWithFrame:contntFrame];
    [contentView setMouseListener:self];
    [_labelWindow setContentView:contentView];
    [contentView release];
    
    return _labelWindow;
}

- (void)_updateLabelWindowWithMousePoint:(NSPoint)point
{
#if 0
    // Get label window
    NSWindow*   labelWindow;
    labelWindow = [self _labelWindow];
    
    // When dragging
    if (_draggingItem) {
        // Hide label window
        if ([labelWindow isVisible]) {
            [labelWindow orderOut:self];
        }
        return;
    }
    
    // Find tab index under mouse
    int     index;
    NSRect  tabFrame;
    for (index = 0; index < [_tabFrames count]; index++) {
        tabFrame = [[_tabFrames objectAtIndex:index] rectValue];
        if (NSPointInRect(point, tabFrame)) {
            break;
        }
    }
    
    // When nothing is under mouse
    if (index >= [_tabFrames count]) {
        // Hide label window
        if ([labelWindow isVisible]) {
            [labelWindow orderOut:self];
        }
        return;
    }
    
    // Get tab view item
    HMTabViewItem*  tabViewItem;
    tabViewItem = [_tabView tabViewItemAtIndex:index];
    
    // Get label and sub label
    NSString*   label;
    NSString*   subLabel;
    label = [tabViewItem label];
    subLabel = [tabViewItem subLabel];
    
    // Set label
    HMLabelContentView* contentView;
    BOOL                changed = NO;
    contentView = [labelWindow contentView];
    if (![label isEqualToString:[contentView label]]) {
        [contentView setLabel:label];
        changed = YES;
    }
    if (![subLabel isEqualToString:[contentView subLabel]]) {
        [contentView setSubLabel:subLabel];
        changed = YES;
    }
    if (changed) {
        [contentView sizeToFit];
    }
    
    // Set center point
    NSRect  frame;
    NSPoint centerPoint;
    frame = tabFrame;
    frame = [self convertRect:frame toView:nil];
    frame.origin = [[self window] convertBaseToScreen:frame.origin];
    centerPoint = NSMakePoint(
            frame.origin.x + frame.size.width / 2.0f, 
            frame.origin.y + frame.size.height / 2.0f);
    [contentView setCenterPoint:centerPoint];
    
    // Show label window
    [labelWindow orderFront:self];
#endif
}

- (void)updateLabelWindow
{
    // Get mouse point
    NSPoint point;
    point = [[self window] convertScreenToBase:[NSEvent mouseLocation]];
    point = [self convertPoint:point fromView:nil];
    
    // Update label window
//    [self _updateLabelWindowWithMousePoint:point];
}

//--------------------------------------------------------------//
#pragma mark -- Delegate --
//--------------------------------------------------------------//

- (id)delegate
{
    return _delegate;
}

- (void)setDelegate:(id)delegate
{
    _delegate = delegate;
}

//--------------------------------------------------------------//
#pragma mark -- Context menu --
//--------------------------------------------------------------//

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    if ([_delegate respondsToSelector:@selector(tabItemGroupView:menuForEvent:)]) {
        return [_delegate tabItemGroupView:self menuForEvent:event];
    }
    
    return [self menu];
}

//--------------------------------------------------------------//
#pragma mark -- NSDraggingSource protocol --
//--------------------------------------------------------------//

- (void)draggedImage:(NSImage*)anImage 
        endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
    // Clear dragging imte
    _draggingItem = nil;
    
    // Update itself
    [self updateTabFrames];
}

//--------------------------------------------------------------//
#pragma mark -- NSDraggingDestination protocol --
//--------------------------------------------------------------//

- (void)_startAnimationWithViewAnimations:(NSArray*)viewAnimations
{
    // Check argument
    if ([viewAnimations count] == 0) {
        return;
    }
    
    // Start animations
    _animation = [[NSViewAnimation alloc] initWithViewAnimations:viewAnimations];
    [_animation setDuration:0.30f];
    [_animation setFrameRate:0.0f];
    [_animation setAnimationBlockingMode:NSAnimationNonblocking];
    [_animation setAnimationCurve:NSAnimationEaseInOut];
    [_animation setDelegate:self];
    [_animation startAnimation];
}

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
    // Initialize values
    _animationIndex = -1;
    
    // Get dragging info
    NSPasteboard*   pboard;
    NSArray*        draggedTypes;
    pboard = [sender draggingPasteboard];
    draggedTypes = [pboard types];
    
    // For PageDock
    if ([draggedTypes containsObject:HMTabItemViewPboardType]) {
        return NSDragOperationMove;
    }
    
    // For othre
    return NSDragOperationCopy;
}

- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
    // Get dragging info
    NSPasteboard*   pboard;
    NSArray*        draggedTypes;
    pboard = [sender draggingPasteboard];
    draggedTypes = [pboard types];
    
#if 0
    // For tab item view
    if ([draggedTypes containsObject:HMTabItemViewPboardType]) {
        // Get dragging location
        NSPoint point;
        point = [self convertPoint:[sender draggingLocation] fromView:nil];
        
        // Get index of dragging item
        int draggingItemIndex;
        draggingItemIndex = [_tabView indexOfTabViewItem:_draggingItem];
        if (draggingItemIndex == NSNotFound) {
            return NSDragOperationMove;
        }
        
        // Find tab frame under mouse
        int     i;
        NSRect  tabFrame;
        for (i = 0; i < [_tabFrames count]; i++) {
            tabFrame = [[_tabFrames objectAtIndex:i] rectValue];
            if (NSPointInRect(point, tabFrame)) {
                break;
            }
        }
        
        if (i < [_tabFrames count] && i != draggingItemIndex) {
            // Decide index to drag
            NSRect  lefthandFrame;
            int     index = i;
            lefthandFrame = tabFrame;
            lefthandFrame.size.width /= 2.0f;
            if (index < draggingItemIndex) {
                if (!NSPointInRect(point, lefthandFrame)) {
                    index += 1;
                }
            }
            else {
                if (NSPointInRect(point, lefthandFrame)) {
                    index -= 1;
                }
            }
            
            // Move dragged item
            if (index != draggingItemIndex) {
                HMTabViewItem*  selectedItem;
                selectedItem = [_tabView selectedTabViewItem];
                [_tabView moveTabViewItemAtIndex:draggingItemIndex toIndex:index];
                [_tabView selectTabViewItem:selectedItem];
                
                [self updateTabFrames];
            }
        }
        else {
        }
        
        return NSDragOperationMove;
    }
#endif
    
    //
    // For other
    //
    
    // Get dragging location
    NSPoint point;
    point = [self convertPoint:[sender draggingLocation] fromView:nil];
    
    // Find tab item frame under mouse
    int i, index;
    NSRect  tabItemFrame;
    for (i = 0; i < [_tabItemViews count]; i++) {
        tabItemFrame = [[_tabItemViews objectAtIndex:i] frame];
        if (point.x < tabItemFrame.origin.x + tabItemFrame.size.width / 2.0f) {
            break;
        }
    }
    index = i;
    
    if (index == _animationIndex) {
        return NSDragOperationCopy;
    }
    
    // Start animation
    NSMutableArray* viewAnimations;
    viewAnimations = [NSMutableArray array];
    for (i = 0; i < [_tabItemViews count]; i++) {
        // Decide target frame
        NSRect  targetFrame;
        targetFrame = [self _tabItemFrameAtIndex:i < index ? i : i + 1];
        
        // Compare with start frame
        HMTabItemView*  tabItemView;
        NSRect          startFrame;
        tabItemView = [_tabItemViews objectAtIndex:i];
        startFrame = [tabItemView frame];
        if (NSEqualRects(targetFrame, startFrame)) {
            continue;
        }
        
        // Configure animation
        NSDictionary*   viewAnimation;
        viewAnimation = [NSDictionary dictionaryWithObjectsAndKeys:
                tabItemView, NSViewAnimationTargetKey, 
                [NSValue valueWithRect:startFrame], NSViewAnimationStartFrameKey, 
                [NSValue valueWithRect:targetFrame], NSViewAnimationEndFrameKey, 
                nil];
        
        [viewAnimations addObject:viewAnimation];
    }
    
    // Start animations
#if 0
    [self _startAnimationWithViewAnimations:viewAnimations];
#endif
    
    // Set animation index
    _animationIndex = index;
    
    return NSDragOperationNone;
}

- (void)draggingExited:(id <NSDraggingInfo>)sender
{
    // Get dragging info
    NSPasteboard*   pboard;
    NSArray*        draggedTypes;
    pboard = [sender draggingPasteboard];
    draggedTypes = [pboard types];
    
    // For tab item view
    if ([draggedTypes containsObject:HMTabItemViewPboardType]) {
        return;
    }
    
    //
    // For other
    //
    
    // Check animation index
    if (_animationIndex == -1) {
        return;
    }
    
    // Start animation
    NSMutableArray* viewAnimations;
    viewAnimations = [NSMutableArray array];
    
    int i;
    for (i = 0; i < [_tabItemViews count]; i++) {
        // Decide target frame
        NSRect  targetFrame;
        targetFrame = [self _tabItemFrameAtIndex:i];
        
        // Compare with start frame
        HMTabItemView*  tabItemView;
        NSRect          startFrame;
        tabItemView = [_tabItemViews objectAtIndex:i];
        startFrame = [tabItemView frame];
        if (NSEqualRects(targetFrame, startFrame)) {
            continue;
        }
        
        // Cnfigure animation
        NSDictionary*   viewAnimation;
        viewAnimation = [NSDictionary dictionaryWithObjectsAndKeys:
                tabItemView, NSViewAnimationTargetKey, 
                [NSValue valueWithRect:startFrame], NSViewAnimationStartFrameKey, 
                [NSValue valueWithRect:targetFrame], NSViewAnimationEndFrameKey, 
                nil];
        
        [viewAnimations addObject:viewAnimation];
    }
    
    // Start animations
#if 0
    [self _startAnimationWithViewAnimations:viewAnimations];
#endif
    
    // Clear animation index
    _animationIndex = -1;
}

- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
    return YES;
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
    // Get dragging info
    NSPasteboard*   pboard;
    NSArray*        draggedTypes;
    pboard = [sender draggingPasteboard];
    draggedTypes = [pboard types];
    
    // For tab item view
    if ([draggedTypes containsObject:HMTabItemViewPboardType]) {
        return YES;
    }
    
    //
    // For other
    //
    
    // Notify to delegate
    id  delegate;
    delegate = [_tabView delegate];
    if ([delegate respondsToSelector:@selector(hmTabView:insertPboard:atTabIndex:)]) {
//        [delegate hmTabView:_tabView insertPboard:pboard atTabIndex:_animationIndex];
        [delegate hmTabView:_tabView insertPboard:pboard atTabIndex:[_tabView numberOfTabViewItems]];
    }
    
    // Update tab frames
    [self updateTabFrames];
    
    return YES;
}

- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
    // Clear animation index
    _animationIndex = -1;
}

//--------------------------------------------------------------//
#pragma mark -- NSAnimation delegate --
//--------------------------------------------------------------//

- (void)_animationDidFinish:(NSAnimation*)animation
{
    // For insertion animation
    if (_animation == animation) {
        _animation = nil;
        [animation release];
        
        return;
    }
    
    // For fade in animation
    if ([_fadeInAnimations containsObject:animation]) {
        // Get view animation
        NSEnumerator*   enumerator;
        NSDictionary*   viewAnimation;
        enumerator = [[(NSViewAnimation*)animation viewAnimations] objectEnumerator];
        while (viewAnimation = [enumerator nextObject]) {
            // Show tab item view
            NSView* tabItemView;
            tabItemView = [viewAnimation objectForKey:HMTabItemViewKey];
            [tabItemView setHidden:NO];
            
            // Remove image view
            NSView* imageView;
            imageView = [viewAnimation objectForKey:NSViewAnimationTargetKey];
            [imageView removeFromSuperview];
        }
        
        // Remove animation
        [_fadeInAnimations removeObject:animation];
        
        return;
    }
    
    // For fade out animation
    if ([_fadeOutAnimations containsObject:animation]) {
        // Get view animation
        NSEnumerator*   enumerator;
        NSDictionary*   viewAnimation;
        enumerator = [[(NSViewAnimation*)animation viewAnimations] objectEnumerator];
        while (viewAnimation = [enumerator nextObject]) {
            // Remove image view
            NSView* imageView;
            imageView = [viewAnimation objectForKey:NSViewAnimationTargetKey];
            [imageView removeFromSuperview];
        }
        
        // Remove animation
        [_fadeOutAnimations removeObject:animation];
        
        // Update tab frames
        [self updateTabFrames];
        
        return;
    }
}

- (void)animationDidEnd:(NSAnimation*)animation
{
    [self _animationDidFinish:animation];
}

- (void)animationDidStop:(NSAnimation*)animation
{
    [self _animationDidFinish:animation];
}

- (float)animation:(NSAnimation*)animation valueForProgress:(NSAnimationProgress)progress
{
    // For fade in animation
    if ([_fadeInAnimations containsObject:animation]) {
        // Get view animation
        NSEnumerator*   enumerator;
        NSDictionary*   viewAnimation;
        enumerator = [[(NSViewAnimation*)animation viewAnimations] objectEnumerator];
        while (viewAnimation = [enumerator nextObject]) {
            // Set alpha
            HMImageView*    imageView;
            imageView = [viewAnimation objectForKey:NSViewAnimationTargetKey];
            [imageView setAlpha:progress];
        }
    }
    
    // For fade out animation
    if ([_fadeOutAnimations containsObject:animation]) {
        // Get view animation
        NSEnumerator*   enumerator;
        NSDictionary*   viewAnimation;
        enumerator = [[(NSViewAnimation*)animation viewAnimations] objectEnumerator];
        while (viewAnimation = [enumerator nextObject]) {
            // Set alpha
            HMImageView*    imageView;
            imageView = [viewAnimation objectForKey:NSViewAnimationTargetKey];
            [imageView setAlpha:1.0f - progress];
        }
    }
    
    return progress;
}

//--------------------------------------------------------------//
#pragma mark -- HMMouseMoveListener protocol --
//--------------------------------------------------------------//

- (void)hmWindow:(NSWindow*)window mouseMoved:(NSEvent*)event
{
    // For no thumbnail
    if (![_tabView hasThumbnail]) {
        return;
    }
    
    // Get mouse point
    NSPoint point;
    point = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // Update label window
    [self _updateLabelWindowWithMousePoint:point];
}

@end
