/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Chimera code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#import "MVPreferencesMultipleIconView.h"
#import "MVPreferencesController.h"
#import "ImageAdditions.h"

@interface MVPreferencesMultipleIconView (MVPreferencesMultipleIconViewPrivate)
- (void) _focusFirst;
- (void) _focusLast;
- (unsigned int) _iconsWide;
- (unsigned int) _numberOfIcons;
- (unsigned int) _numberOfRows;
- (BOOL) _isIconSelectedAtIndex:(unsigned int) index;
- (BOOL) _column:(unsigned int *) column andRow:(unsigned int *) row forIndex:(unsigned int) index;
- (NSRect) _boundsForIndex:(unsigned int) index;
- (BOOL) _iconImage:(NSImage **) image andName:(NSString **) name forIndex:(unsigned int) index;
- (BOOL) _iconImage:(NSImage **) image andName:(NSString **) name andIdentifier:(NSString **) identifier forIndex:(unsigned int) index;
- (void) _drawIconAtIndex:(unsigned int) index drawRect:(NSRect) drawRect;
- (void) _sizeToFit;
- (BOOL) _dragIconIndex:(unsigned int) index event:(NSEvent *) event;
- (BOOL) _dragIconImage:(NSImage *) iconImage andName:(NSString *) name event:(NSEvent *) event;
- (BOOL) _dragIconImage:(NSImage *) iconImage andName:(NSString *) name andIdentifier:(NSString *) identifier event:(NSEvent *) event;
@end

@interface MVPreferencesController (MVPreferencesControllerPrivate)
- (IBAction) _selectPreferencePane:(id) sender;
- (void) _resizeWindowForContentView:(NSView *) view;
- (NSImage *) _imageForPaneBundle:(NSBundle *) bundle;
- (NSString *) _paletteLabelForPaneBundle:(NSBundle *) bundle;
- (NSString *) _labelForPaneBundle:(NSBundle *) bundle;
@end

@implementation MVPreferencesMultipleIconView

const NSSize buttonSize = { 81.0, 75.0 };
const NSSize iconSize = { 32.0, 32.0 };
const unsigned int titleBaseline = 15;
const unsigned int iconBaseline = 30;
const unsigned int bottomBorder = 15;

- (id) initWithFrame:(NSRect) rect {
	if( ( self = [super initWithFrame:rect] ) ) {
		pressedIconIndex = NSNotFound;
		focusedIndex = NSNotFound;
		selectedPane = nil;
	}
	return self;
}

- (void) setPreferencesController:(MVPreferencesController *) newPreferencesController {
	[preferencesController autorelease];
	preferencesController = [newPreferencesController retain];
	[self setNeedsDisplay:YES];
}

- (void) setPreferencePanes:(NSArray *) newPreferencePanes {
	[preferencePanes autorelease];
	preferencePanes = [newPreferencePanes retain];
	[self _sizeToFit];
	[self setNeedsDisplay:YES];
}

- (NSArray *) preferencePanes {
	return preferencePanes;
}

- (void) setSelectedPane:(NSBundle *) newSelectedPane {
	selectedPane = newSelectedPane;
	[self setNeedsDisplay:YES];
}

- (void) mouseDown:(NSEvent *) event {
	NSPoint eventLocation;
	NSRect slopRect;
	const float dragSlop = 4.;
	unsigned int index;
	NSRect buttonRect;
	BOOL mouseInBounds = NO;

	eventLocation = [self convertPoint:[event locationInWindow] fromView:nil];
	slopRect = NSInsetRect( NSMakeRect( eventLocation.x, eventLocation.y, 1., 1. ), -dragSlop, -dragSlop );

	index = floor( eventLocation.x / buttonSize.width ) + ( floor( eventLocation.y / buttonSize.height ) * [self _iconsWide] );
	buttonRect = [self _boundsForIndex:index];
	if( index >= ( [self _iconsWide] * ( floor( eventLocation.y / buttonSize.height ) + 1 ) ) ) return;
	if( ! NSWidth( buttonRect ) ) return;

	pressedIconIndex = index;
	[self setNeedsDisplayInRect:[self _boundsForIndex:index]];

	while( 1 ) {
		NSEvent *nextEvent;
		NSPoint nextEventLocation;
		unsigned int newPressedIconIndex;

		nextEvent = [NSApp nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES];

		nextEventLocation = [self convertPoint:[nextEvent locationInWindow] fromView:nil];
		mouseInBounds = NSMouseInRect(nextEventLocation, buttonRect, [self isFlipped]);
		newPressedIconIndex = ( mouseInBounds ? index : NSNotFound );
		if( newPressedIconIndex != pressedIconIndex ) {
			pressedIconIndex = newPressedIconIndex;
			[self setNeedsDisplayInRect:[self _boundsForIndex:pressedIconIndex]];
		}

		if( [nextEvent type] == NSLeftMouseUp ) break;
		else if( ! NSMouseInRect( nextEventLocation, slopRect, NO ) ) {
			if( [self _dragIconIndex:index event:event] ) {
				mouseInBounds = NO;
				break;
			}
		}
	}

	pressedIconIndex = NSNotFound;
	[self setNeedsDisplayInRect:[self _boundsForIndex:index]];

	if( mouseInBounds )
		[preferencesController selectPreferencePaneByIdentifier:[[preferencePanes objectAtIndex:index] bundleIdentifier]];
}

#define kTabCharCode 9
#define kShiftTabCharCode 25
#define kSpaceCharCode 32

- (void) keyDown:(NSEvent *) theEvent {
	NSView *nextView = nil;
	if( [[theEvent charactersIgnoringModifiers] characterAtIndex:0] == kTabCharCode ) {
		[self setKeyboardFocusRingNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
		if( focusedIndex == NSNotFound && [self _numberOfIcons] ) focusedIndex = 0;
		else if( ! ( [theEvent modifierFlags] & NSShiftKeyMask ) ) focusedIndex++;
		if( focusedIndex >= [self _numberOfIcons] ) {
			if( ( nextView = [self nextValidKeyView] ) ) {
				[[self window] makeFirstResponder:nextView];
				focusedIndex = NSNotFound;
				if( [nextView isKindOfClass:[MVPreferencesMultipleIconView class]] )
					[(MVPreferencesMultipleIconView *)nextView _focusFirst];
			} else focusedIndex = 0;
		}
		if( ! [self _numberOfIcons] ) focusedIndex = NSNotFound;
		[self setNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
	} else if( [[theEvent charactersIgnoringModifiers] characterAtIndex:0] == kShiftTabCharCode ) {
		[self setKeyboardFocusRingNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
		if( focusedIndex == NSNotFound && [self _numberOfIcons] ) focusedIndex = [self _numberOfIcons] - 1;
		else if( [theEvent modifierFlags] & NSShiftKeyMask ) focusedIndex--;
		if( (signed) focusedIndex < 0 && [self _numberOfIcons] ) {
			if( ( nextView = [self previousValidKeyView] ) ) {
				[[self window] makeFirstResponder:nextView];
				focusedIndex = NSNotFound;
				if( [nextView isKindOfClass:[MVPreferencesMultipleIconView class]] )
					[(MVPreferencesMultipleIconView *)nextView _focusLast];
			} else focusedIndex = [self _numberOfIcons] - 1;
		}
		if( ! [self _numberOfIcons] ) focusedIndex = NSNotFound;
		[self setNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
	} else if( [[theEvent charactersIgnoringModifiers] characterAtIndex:0] == kSpaceCharCode ) {
		if( focusedIndex != NSNotFound && focusedIndex < [self _numberOfIcons] && focusedIndex >= 0 ) {
			[preferencesController selectPreferencePaneByIdentifier:[[preferencePanes objectAtIndex:focusedIndex] bundleIdentifier]];
			focusedIndex = NSNotFound;
		}
	}
}

- (BOOL) acceptsFirstResponder {
	return YES;
}

- (BOOL) becomeFirstResponder {
	focusedIndex = NSNotFound;
	return YES;
}

- (BOOL) resignFirstResponder {
	[self setKeyboardFocusRingNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
	focusedIndex = NSNotFound;
	return YES;
}

- (void) drawRect:(NSRect) rect {
	unsigned int paneIndex, paneCount;

	paneCount = [self _numberOfIcons];
	for( paneIndex = 0; paneIndex < paneCount; paneIndex++ )
		[self _drawIconAtIndex:paneIndex drawRect:rect];
}

- (BOOL) isFlipped {
	return YES;
}

- (BOOL) isOpaque {
	return NO;
}

- (int) tag {
	return tag;
}

- (void) setTag:(int) newTag {
	tag = newTag;
}

- (NSDragOperation) draggingSourceOperationMaskForLocal:(BOOL) flag {
	return NSDragOperationMove;
}

- (void) draggedImage:(NSImage *)image endedAt:(NSPoint) screenPoint operation:(NSDragOperation) operation {
}

- (BOOL) ignoreModifierKeysWhileDragging {
	return YES;
}
@end

@implementation MVPreferencesMultipleIconView (MVPreferencesMultipleIconViewPrivate)
- (void) _focusFirst {
	focusedIndex = 0;
	[self setNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
}

- (void) _focusLast {
	focusedIndex = [self _numberOfIcons] - 1;
	[self setNeedsDisplayInRect:[self _boundsForIndex:focusedIndex]];
}

- (unsigned int) _iconsWide {
	return NSWidth( [self bounds] ) / buttonSize.width;
}

- (unsigned int) _numberOfIcons {
	return [[self preferencePanes] count];
}

- (unsigned int) _numberOfRows {
	unsigned int row = 0, column = 0;
	if( ! [self _column:&column andRow:&row forIndex:[self _numberOfIcons] - 1] ) return 0;
	return row;
}

- (BOOL) _isIconSelectedAtIndex:(unsigned int) index {
	return [[self preferencePanes] objectAtIndex:index] == selectedPane;
}

- (BOOL) _column:(unsigned int *) column andRow:(unsigned int *) row forIndex:(unsigned int) index {
	if( index >= [self _numberOfIcons] ) return NO;

	*row = index / [self _iconsWide];
	*column = index % [self _iconsWide];

	return YES;
}

- (NSRect) _boundsForIndex:(unsigned int) index {
	unsigned int row = 0, column = 0, leftEdge = 0;

	if( ! [self _column:&column andRow:&row forIndex:index] ) return NSZeroRect;

	leftEdge = ( NSWidth( _bounds ) - ( [self _iconsWide] * buttonSize.width ) ) / 2. ;

	return NSMakeRect( ( column * buttonSize.width ) + leftEdge, row * buttonSize.height - (row * 6), buttonSize.width, buttonSize.height );
}

- (BOOL) _iconImage:(NSImage **) image andName:(NSString **) name forIndex:(unsigned int) index {
	NSString *unused;
	return [self _iconImage:image andName:name andIdentifier:&unused forIndex:index];
}

- (BOOL) _iconImage:(NSImage **) image andName:(NSString **) name andIdentifier:(NSString **) identifier forIndex:(unsigned int) index {
	NSDictionary *info = nil;
	NSBundle *pane = nil;

	if( index >= [self _numberOfIcons] ) return NO;

	pane = [[self preferencePanes] objectAtIndex:index];
	info = [pane infoDictionary];
	*image = [preferencesController _imageForPaneBundle:pane];
	*name = [preferencesController _paletteLabelForPaneBundle:pane];
	*identifier = [pane bundleIdentifier];

	return YES;
}

- (void) _drawIconAtIndex:(unsigned int) index drawRect:(NSRect) drawRect {
	NSImage *image;
	NSString *name;
	unsigned int row, column;
	NSPoint drawPoint;
	float nameHeight;
	NSRect buttonRect, destinationRect;
	NSDictionary *attributesDictionary;
	NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
	CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

	buttonRect = [self _boundsForIndex:index];
	if( ! NSIntersectsRect( buttonRect, drawRect ) ) return;
	if( ! [self _iconImage:&image andName:&name forIndex:index] ) return;
	if( ! [self _column:&column andRow:&row forIndex:index] ) return;

	// Draw dark gray rectangle around currently selected icon (for MultipleIconView)
	if( [self _isIconSelectedAtIndex:index] ) {
		[[NSColor colorWithCalibratedWhite:0.8 alpha:0.75] set];
		NSRectFillUsingOperation(buttonRect, NSCompositeSourceOver);
	}

	if( focusedIndex == index ) {
		CGContextSaveGState( context );
		NSSetFocusRingStyle( NSFocusRingAbove );
	}

	// Draw icon, dark if it is currently being pressed
	destinationRect = NSIntegralRect( NSMakeRect( NSMidX( buttonRect) - iconSize.width / 2., NSMaxY( buttonRect ) - iconBaseline - iconSize.height, iconSize.width, iconSize.height ) );
	destinationRect.size = iconSize;
	if( index != pressedIconIndex && image )
    [image drawFlippedInRect:destinationRect operation:NSCompositeSourceOver fraction:1.];
	else if( image )
  {
		NSImage *darkImage;
		NSSize darkImageSize;

		darkImage = [image copy];
		darkImageSize = [darkImage size];
		[darkImage lockFocus];
    {
			[[NSColor blackColor] set];
			NSRectFillUsingOperation( NSMakeRect( 0., 0., darkImageSize.width, darkImageSize.height ), NSCompositeSourceIn );
			[darkImage unlockFocus];
		}

		[darkImage drawFlippedInRect:destinationRect operation:NSCompositeSourceOver fraction:1.];
		[image drawFlippedInRect:destinationRect operation:NSCompositeSourceOver fraction:.6666667];
		[darkImage release];
	}
	if( focusedIndex == index )
    CGContextRestoreGState( context );

	// Draw text
	[paraStyle setAlignment:NSCenterTextAlignment];
	attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont toolTipsFontOfSize:11.], NSFontAttributeName, paraStyle, NSParagraphStyleAttributeName, nil];
	nameHeight = [[NSFont toolTipsFontOfSize:11.] defaultLineHeightForFont];
	drawPoint = NSMakePoint( rint( buttonSize.width + 2 * NSMinX( buttonRect ) - NSWidth( _bounds ) - 1 ), rint( NSMaxY( buttonRect ) - titleBaseline - nameHeight ) );
	[name drawAtPoint:drawPoint withAttributes:attributesDictionary];
}

- (void) _sizeToFit {
	if( ! [self preferencePanes] ) return;
	[self setFrameSize:NSMakeSize( NSWidth( _bounds ), NSMaxY( [self _boundsForIndex:[self _numberOfIcons] - 1] ) + bottomBorder ) ];
}

- (BOOL) _dragIconIndex:(unsigned int) index event:(NSEvent *) event {
	NSImage *iconImage;
	NSString *name;
	NSString *identifier;

	if( ! [self _iconImage:&iconImage andName:&name andIdentifier:&identifier forIndex:index] )
		return YES;

	return [self _dragIconImage:iconImage andName:name andIdentifier:identifier event:event];
}

- (BOOL) _dragIconImage:(NSImage *) iconImage andName:(NSString *) name event:(NSEvent *) event {
	return [self _dragIconImage:iconImage andName:name andIdentifier:name event:event];
}

- (BOOL) _dragIconImage:(NSImage *) iconImage andName:(NSString *) name andIdentifier:(NSString *) identifier event:(NSEvent *) event {
	NSImage *dragImage;
	NSPasteboard *pasteboard;
	NSPoint dragPoint, startPoint;
	NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

	dragImage = [[NSImage alloc] initWithSize:buttonSize];
	[dragImage lockFocus];
  {
		float nameHeight;
		NSSize nameSize;
		NSDictionary *attributesDictionary;

		[iconImage drawInRect:NSMakeRect( buttonSize.width / 2. - iconSize.width / 2., iconBaseline, iconSize.width, iconSize.height ) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:.5];

		[paraStyle setAlignment:NSCenterTextAlignment];
		attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont toolTipsFontOfSize:11.], NSFontAttributeName, [[NSColor blackColor] colorWithAlphaComponent:.5], NSForegroundColorAttributeName, paraStyle, NSParagraphStyleAttributeName, nil];
		nameSize = [name sizeWithAttributes:attributesDictionary];
		nameHeight = [[NSFont toolTipsFontOfSize:11.] defaultLineHeightForFont];
		[name drawAtPoint:NSMakePoint( 0., nameHeight + titleBaseline - nameSize.height ) withAttributes:attributesDictionary];
		[dragImage unlockFocus];
	}

	pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
	[pasteboard declareTypes:[NSArray arrayWithObject:@"NSToolbarIndividualItemDragType"] owner:nil];
	[pasteboard setString:identifier forType:@"NSToolbarItemIdentiferPboardType"];

	dragPoint = [self convertPoint:[event locationInWindow] fromView:nil];
	startPoint = [self _boundsForIndex:[preferencePanes indexOfObject:[NSBundle bundleWithIdentifier:identifier]]].origin;
	startPoint.y += buttonSize.height;
	[self setNeedsDisplayInRect:[self _boundsForIndex:pressedIconIndex]];
	pressedIconIndex = NSNotFound;
	[self dragImage:dragImage at:startPoint offset:NSZeroSize event:event pasteboard:pasteboard source:self slideBack:YES];

	return YES;
}
@end