//
//  NSMenuView.m
//  mySTEP
//
//  Created by Dr. H. Nikolaus Schaller on Thu Mar 27 2003.
//  Copyright (c) 2003 DSITRI. All rights reserved.
//

///// selecting a menu item should set setDefaultButtonCell: so that 'return' selects

#import <AppKit/NSMenuView.h>
#import <AppKit/NSMenuItemCell.h>
#import <AppKit/NSScreen.h>
#import <AppKit/NSStatusBar.h>
#import <AppKit/NSStatusItem.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSBezierPath.h>

#import "NSAppKitPrivate.h"

#define VERTICAL_PADDING		3.0		// padding at top/bottom of vertical menus
#define HORIZONTAL_PADDING		3.0		// padding at left/right of menu items
#define SHORT_CLICK_INTERVAL	0.4		// in seconds

NSTimeInterval mouseDownTimeStamp;				// when did mouse go down the first time with no open menus before?
static NSMenuView *_currentlyOpenMenuView;		// in which view

@implementation NSMenuView

+ (NSMenuView *) _currentlyOpenMenuView;	{ return _currentlyOpenMenuView; }

+ (void) _deactivate;
{
	if(_currentlyOpenMenuView)
		{
		[_currentlyOpenMenuView detachSubmenu];	// detach all open submenu items
		_currentlyOpenMenuView=nil;
		}
}

- (BOOL) _isResizingHorizontally; { return isResizingHorizontally; }

- (void) _setHorizontalResize:(BOOL) flag;
{ // if set resize horizontal width as needed - otherwise leave untouched
#if 0
	NSLog(@"setHorizontalResize:%d", flag);
#endif
	if(isResizingHorizontally == flag)
		return;	// unchanged
	isResizingHorizontally=flag;
	needsSizing=YES;
}

- (BOOL) _isStatusBar; { return isStatusBar; }

- (void) _setStatusBar:(BOOL) flag;
{
	if(isStatusBar==flag)
		return;	// unchanged
	isStatusBar=flag;
	needsSizing=YES;
}

- (BOOL) _isContextMenu; { return isContextMenu; }
- (void) _setContextMenu:(BOOL) flag; { isContextMenu=flag; }

// standard extensions

+ (float) menuBarHeight;
{
	// 240 -> 16
	// 320 -> 16
	// 480 -> 24
	// 640 -> 32
	// 768 -> 38
	// 1024 -> 51
	static int h=0;
	if(h == 0 && [[NSScreen screens] count] > 0)
		{
		h=[[[NSScreen screens] objectAtIndex:0] frame].size.height*0.05;  // 5% of screen size
		if(h < 12)
			h=12;	// for small screens
		if(h > 24)
			h=24;	// for large screens
		}
	return (float) h;
}

- (void) attachSubmenuForItemAtIndex:(int) index;
{
	NSWindow *win;
	NSRect r;
	NSMenu *submenu=[[menu itemAtIndex:index] submenu];
#if 0
	NSLog(@"attachSubmenuForItemAtIndex %d", index);
#endif
	[[submenu delegate] menuNeedsUpdate:submenu];	// allow to update entries before really opening
	if([submenu numberOfItems] < 1)
		return; // ignore empty submenus
	[self detachSubmenu];	// detach any other submenus before attaching new submenu
	[submenu update];		// enable/disable menu items
	/*
	 create window
	 */
	win=[[[NSWindow alloc] initWithContentRect:NSMakeRect(0.0, 0.0, 50.0, 50.0)
									 styleMask:NSBorderlessWindowMask
									   backing:NSBackingStoreBuffered
										 defer:YES] retain];	// will be released on close
	[win setLevel:NSSubmenuWindowLevel];
#if 0
	NSLog(@"win=%@", win);
	NSLog(@"index=%d rect=%@", index, NSStringFromRect([self rectOfItemAtIndex:index]));
	NSLog(@"converted rect=%@", NSStringFromRect([self convertRect:[self rectOfItemAtIndex:index] toView:nil]));
	NSLog(@"autodisplay=%d", [win isAutodisplay]);
#endif
#if 1
	[win setTitle:[submenu title]];
#endif
	attachedMenuView=[[isa alloc] initWithFrame:NSMakeRect(0.0, 0.0, 50.0, 50.0)];	// make new NSMenuView of arbitrary size
	[win setContentView:attachedMenuView];	// make content view
#if 0
	NSLog(@"attachedMenuView=%@", attachedMenuView);
#endif
	[attachedMenuView setHorizontal:NO];	// make submenus always vertical
#if 0
	NSLog(@"was setHorizontal:NO");
#endif
	[attachedMenuView setMenu:submenu];		// define to manage selected submenu
	r=[self convertRect:[self rectOfItemAtIndex:index] toView:nil];
	r.origin=[window convertBaseToScreen:r.origin];  // convert to screen coordinates
	// might offset y if menu is second tier submenu of the first entry
	//
	// FIXME: check if menu vertically fits on screen
	// if not, draw up and down arrow buttons at top and bottom
	// and make menu scrollable
	//
#if 0
	NSLog(@"screen rect=%@", NSStringFromRect(r));
#endif
	[attachedMenuView setWindowFrameForAttachingToRect:r
					  onScreen:[window screen]
					  preferredEdge:(isHorizontal?NSMinYEdge:NSMaxXEdge)	// default: below or to the right
					  popUpSelectedItem:-1];
	[win orderFront:self];  // finally, make it visible
	//// [attachedMenuView sizeToFit]?
#if 0
	NSLog(@"attachSubmenu done");
#endif
}

- (NSMenu *) attachedMenu; { return [attachedMenuView menu]; }

- (NSMenuView *) attachedMenuView; { return attachedMenuView; }

- (void) detachSubmenu;
{
	NSWindow *win;
	if(!attachedMenuView)
		return;		// none attached
	[self setHighlightedItemIndex:-1];				// remove any highlighting
	[[self attachedMenu] setSupermenu:nil];			// detach supermenu
	[attachedMenuView detachSubmenu];				// recursively detach
#if 0
	NSLog(@"detachSubmenu %@", attachedMenuView);
#endif
	win=[attachedMenuView window];
	attachedMenuView=nil;	// no longer attached
	[win close];			// finally release window and remove everything from window hierarchy
}

- (NSFont *) font; { return font?font:(isStatusBar?[NSFont menuFontOfSize:0.0]:[NSFont menuBarFontOfSize:0.0]); }

- (int) highlightedItemIndex; { return highlightedItemIndex; }

- (float) horizontalEdgePadding; { return horizontalEdgePadding; }

- (float) imageAndTitleOffset; { if(needsSizing) [self sizeToFit]; return imageAndTitleOffset; }

- (float) imageAndTitleWidth; { if(needsSizing) [self sizeToFit]; return imageAndTitleWidth; }

- (int) indexOfItemAtPoint:(NSPoint) point
{
	int i;
	int nc=[cells count];
	for(i=0; i<nc; i++)
		if(NSPointInRect(point, [self rectOfItemAtIndex:i]))	// CHECKME: mouseinpoint?
			return i;	// found
	return -1;
}

- (NSRect) innerRect;
{
	NSRect r=[self bounds];
	r.origin.y+=2.0;
	r.size.height-=4;
	return r;
#if 0
	if (_horizontal == NO)
		{
		return NSMakeRect(_bounds.origin.x + _leftBorderOffset, _bounds.origin.y,
					_bounds.size.width - _leftBorderOffset, _bounds.size.height);
		}
	else
		{
		return NSMakeRect(_bounds.origin.x, _bounds.origin.y +  _leftBorderOffset,
					_bounds.size.width, _bounds.size.height - _leftBorderOffset);
		}
#endif
}

- (id) initAsTearOff; { return NIMP; }

- (id) initWithFrame:(NSRect) fr
{ // super is NSView
#if 0
	NSLog(@"MenuView initWithFrame:%@", NSStringFromRect(fr));
#endif
	if((self=[super initWithFrame:fr]))
		{
#if 0
		NSLog(@"initWithFrame:%@ bounds:%@", NSStringFromRect([self frame]), NSStringFromRect([self bounds]));
#endif
		cells=[[NSMutableArray arrayWithCapacity:10] retain];
		rectOfCells=NULL;
		highlightedItemIndex=-1;				// nothing highlighted
		[self setHorizontal:NO];				// initially vertical
		[self setHorizontalEdgePadding:3.0];	// default
		[self _setHorizontalResize:YES];			// default
		[self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];	// resize us with our superview
		}
	return self;
}

- (BOOL) isAttached; { return [menu isAttached]; }

- (BOOL) isHorizontal; { return isHorizontal; }

- (BOOL) isTornOff; { return NO; }

- (void) itemAdded:(NSNotification *) notification;
{ // new menu item has been added
	int pos=[[[notification userInfo] objectForKey:@"NSMenuItemIndex"] intValue];	// position
	id c=[[[NSMenuItemCell alloc] init] autorelease];		// make new cell
#if 0
	NSLog(@"itemAdded - pos=%d, cell=%@", pos, c);
#endif
	[cells insertObject:c atIndex:pos];				// make new slot (will be replaced with c)
	rectOfCells=(NSRect *) realloc(rectOfCells, [cells count]*sizeof(NSRect));	// adjust size
	rectOfCells[pos]=NSZeroRect;	// clear
	// might have to update highlighting!!
	// mark new cell for needing redraw?!
	[self setMenuItemCell:c forItemAtIndex:pos];	// to add all cell connections and updates
}

- (void) itemChanged:(NSNotification *) notification;
{
	int pos=[[[notification userInfo] objectForKey:@"NSMenuItemIndex"] intValue];	// position
	id cell=[self menuItemCellForItemAtIndex:pos];					// get cell
#if 0
	NSMenuItem *item=[[menu itemArray] objectAtIndex:pos];							// changed item
	NSLog(@"itemChanged - pos=%d, item=%@", pos, item);
#endif
	[cell setNeedsSizing:YES];					// we may need to resize
	needsSizing=YES;							// recalculate
	[self setNeedsDisplayForItemAtIndex:pos];   // redisplay changed item
}

- (void) itemRemoved:(NSNotification *) notification;
{
	int pos=[[[notification userInfo] objectForKey:@"NSMenuItemIndex"] intValue];	// position
	id cell=[self menuItemCellForItemAtIndex:pos];					// get cell
#if 0
	NSMenuItem *item=[[menu itemArray] objectAtIndex:pos];							// changed item
	NSLog(@"itemRemoved - pos=%d, removeditem=%@ retainCount=%d", pos, item, [item retainCount]);
#endif
	[cell setMenuItem:nil];	// remove reference to menu item to be shown
	[cell setMenuView:nil];	// remove reference to myself
	[cells removeObjectAtIndex:pos];	// this will dealloc the itemCell
	rectOfCells=(NSRect *) realloc(rectOfCells, [cells count]*sizeof(NSRect));	// adjust size
	needsSizing=YES;	// recalculate
	[self setNeedsDisplay:YES];
}

- (float) keyEquivalentOffset; { if(needsSizing) [self sizeToFit]; return keyEquivalentOffset; }

- (float) keyEquivalentWidth; { if(needsSizing) [self sizeToFit]; return keyEquivalentWidth; }

- (NSPoint) locationForSubmenu:(NSMenu *) submenu;
{
	NSRect p;
	int idx;
#if 1
	NSLog(@"locationForSubmenu");
	NSLog(@"should not be used!");
#endif
	if(needsSizing)
		[self sizeToFit];
	idx=[menu indexOfItemWithSubmenu:submenu];	// locate submenu
	if(idx < 0)
		return [self frame].origin;
	p=rectOfCells[idx];		// location of referenced menu item
	if(isHorizontal)
		{ // below/above
		p.origin.y-=p.size.height;	// below
		}
	else
		{ // right/left
		p.origin.x+=p.size.width;	// right of menu
		// should check if submenu itself fits there
		}
	return p.origin;
}

- (NSMenu *) menu; { return menu; }

- (NSMenuItemCell *) menuItemCellForItemAtIndex:(int) index;
{
	return [cells objectAtIndex:index];
}

- (BOOL) needsSizing; { return needsSizing; }

- (void) performActionWithHighlighingForItemAtIndex:(int) index;
{
	NSMenuItemCell *c=[self menuItemCellForItemAtIndex:index];
	[self setHighlightedItemIndex:index];
	[self display];
	[c performClick:[c menuItem]];
	[self setHighlightedItemIndex:-1];	// remove any highlighting
	[self display];
}

- (BOOL) performKeyEquivalent:(NSEvent*)event
{ // find a menu item that responds to this key event
	return [menu performKeyEquivalent:event];
}

- (NSRect) rectOfItemAtIndex:(int) index;
{
	if(needsSizing)
		[self sizeToFit];
	NSCParameterAssert(index >= 0 && index < [cells count]);		
	return rectOfCells[index];
}

- (void) setFont:(NSFont *) f;
{
	if(!f) f=[NSFont menuFontOfSize:0];	// susbtitute default
	if(font == f)
		return;
	ASSIGN(font, f);
	needsSizing=YES;
}

- (void) setHighlightedItemIndex:(int) index;
{
	NSMenuItemCell *c;
	if(highlightedItemIndex == index)
		return;	// no change
	if(highlightedItemIndex >= 0)
		{ // lowlight old
		[[self menuItemCellForItemAtIndex:highlightedItemIndex] setHighlighted:NO];		// remove highlighting
		[self setNeedsDisplayForItemAtIndex:highlightedItemIndex];
		}
	highlightedItemIndex=index;
	if(highlightedItemIndex < 0)
		return;	// no highlighted item
	c=[self menuItemCellForItemAtIndex:highlightedItemIndex];
	// FIXME: enable items having private view
	if([[c menuItem] isSeparatorItem] || ![[c menuItem] isEnabled])
		{ // don't highlight separators or disabled items
		highlightedItemIndex=-1;
		return;
		}
	[c setHighlighted:YES];	// add new highlighting
	[c setNeedsDisplay:YES];				// mark cell to redraw itself
	[self setNeedsDisplayInRect:[self rectOfItemAtIndex:highlightedItemIndex]];	// we need redrawing
}

- (void) setHorizontal:(BOOL) flag;
{
	int i;
	int nc;
	if(isHorizontal==flag)
		return;	// no change
#if 0
	NSLog(@"setHorizontal:%d", flag);
#endif
	isHorizontal=flag;
	nc=[cells count];
	for(i=0; i<nc; i++)
		[[cells objectAtIndex:i] setNeedsSizing:YES];	// all cells need resizing
	needsSizing=YES;	// resize and rearrange
}

- (void) setHorizontalEdgePadding:(float) pad; { horizontalEdgePadding=pad; needsSizing=YES; }

- (void) setMenu:(NSMenu *) m;
{
	int i, cnt;
#if 0
	NSLog(@"%@ setMenu: %@", self, m);
#endif
	if(menu)
		{ // remove all observers for this menu
		[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMenuDidAddItemNotification object:menu];
		[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMenuDidChangeItemNotification object:menu];
		[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMenuDidRemoveItemNotification object:menu];
		[menu setMenuRepresentation:nil];
		}
	for(i=0, cnt=[cells count]; i<cnt; i++)
		{
		id cell=[cells objectAtIndex:i];
		[cell setMenuItem:nil];
		[cell setMenuView:nil];	// invalidate
		}
	[cells removeAllObjects];	// should dealloc all cells
	[menu autorelease];
	if((menu=[m retain]))
		{ // set new menu
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemAdded:) name:NSMenuDidAddItemNotification object:menu];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemChanged:) name:NSMenuDidChangeItemNotification object:menu];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemRemoved:) name:NSMenuDidRemoveItemNotification object:menu];
		[menu setMenuRepresentation:self];
		for(i=0, cnt=[menu numberOfItems]; i<cnt; i++)
			{ // (re)add all existing menu items by calling itemAdded
			[self itemAdded:[NSNotification	notificationWithName:NSMenuDidAddItemNotification
														  object:menu
														userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:i]
																							 forKey:@"NSMenuItemIndex"]]];
			}
		[menu update];		// auto-enable and resize if needed
		needsSizing=YES;	// surely...
		}
}

- (void) setMenuItemCell:(NSMenuItemCell *) cell forItemAtIndex:(int) index;
{
	[cells replaceObjectAtIndex:index withObject:cell];
	[cell setMenuItem:[[menu itemArray] objectAtIndex:index]];	// make a reference to menu item to be shown
	// FIXME: should we use setMenuRepresentation?
	[cell setMenuView:(NSMenuView *) self];				// make reference to myself
	[cell setFont:font];								// set font
	[cell setHighlighted:highlightedItemIndex==index];	// may be the currently highlighted element
	needsSizing=YES;	// we will need to recalc size - which will also redisplay the full menu
}

- (void) setNeedsDisplayForItemAtIndex:(int) index;
{
	[[cells objectAtIndex:index] setNeedsDisplay:YES];				// mark cell to redraw itself
	[self setNeedsDisplayInRect:[self rectOfItemAtIndex:index]];	// we need redrawing
}

- (void) setNeedsSizing:(BOOL) flag; { needsSizing=flag; }

- (void) setWindowFrameForAttachingToRect:(NSRect) ref
								 onScreen:(NSScreen *) screen
							preferredEdge:(NSRectEdge) edge
						popUpSelectedItem:(int) index;
{
	NSWindow *win=window;
	NSRect mf;  // new menu frame
	NSRect sf=[[win screen] visibleFrame];
#if 0
	NSLog(@"setWindowFrameForAttachingToRect:%@ screen:... edge:%d item:%d", NSStringFromRect(ref), edge, index);
#endif
	if(needsSizing)
		[self sizeToFit];	// this will resize the window
	edge &= 3;
	mf.size=frame.size;   // copy content size
	switch(edge)
		{ // calculate preferred location
		case NSMinXEdge:	// to the left
			mf.origin.x=ref.origin.x-mf.size.width;
			mf.origin.y=ref.origin.y+ref.size.height-mf.size.height+VERTICAL_PADDING; // align top edge
			break;
		case NSMaxXEdge:	// to the right
			mf.origin.x=ref.origin.x+ref.size.width;
			mf.origin.y=ref.origin.y+ref.size.height-mf.size.height+VERTICAL_PADDING; // align top edge
			break;
		case NSMinYEdge:	// below
			mf.origin.x=ref.origin.x;
			mf.origin.y=ref.origin.y-mf.size.height;
			break;
		case NSMaxYEdge:	// above
			mf.origin.x=ref.origin.x;
			mf.origin.y=ref.origin.y+ref.size.height;
			break;
		}
	if(mf.origin.x < 0)		// does not fit to the left - try to the right
		mf.origin.x=ref.origin.x+ref.size.width;
	if(mf.origin.x+mf.size.width > sf.size.width)   // does (still) not fit to the right - try (again) to the left but align right border on horizontal menus
		mf.origin.x=ref.origin.x-mf.size.width+((edge==NSMinYEdge || edge==NSMaxYEdge)?ref.size.width:0.0);
	if(mf.origin.x < 0)
		mf.origin.x=0.0;	// still no fit - give up (could center to screen)
	if(mf.origin.y < 0)		// try above if it does not fit below
		mf.origin.y=ref.origin.y+((edge==NSMinYEdge || edge==NSMaxYEdge)?ref.size.height:0.0);
	if(mf.origin.y+mf.size.height > sf.size.height) // try below
		mf.origin.y=ref.origin.y-mf.size.height;
	if(mf.origin.y < 0)
		mf.origin.y=0.0;   // still no fit - give up
	if(index >= 0)
		{
		// adjust by index - different if horizontal or vertical
		// we can set bounds accordingly
		}
#if 0
	NSLog(@"set frame=%@", NSStringFromRect(mf));
#endif
	[win setFrame:[win frameRectForContentRect:mf] display:NO];	// this will also set our frame&bounds since we are the contentView!
#if 0
	NSLog(@"set frame done");
#endif
}

- (void) _calcMaxWidthOfCellComponents;
{ // get maximum width of all cell components
	unsigned int i;
	unsigned int nc;
	imageAndTitleWidth=0.0;
	keyEquivalentWidth=0.0;
	stateImageWidth=0.0;
	for(i=0, nc=[cells count]; i<nc; i++)
		{ // get element widths and determine their maximum
		NSMenuItemCell *c=[cells objectAtIndex:i];
		float iw, tw, iatw;
		[c setNeedsSizing:YES];		// get latest values
		if((iw=[c stateImageWidth]) > stateImageWidth)
			stateImageWidth=iw;		// new maximum
		iw=[c imageWidth];
		tw=[c titleWidth];
		iatw=iw+tw;	// image and title width
		if(iw > 0.0 && tw > 0.0)
			iatw+=horizontalEdgePadding;	// add padding if both elements are present
		if(iatw > imageAndTitleWidth)
			imageAndTitleWidth=iatw;					// new maximum
		if((iw=[c keyEquivalentWidth]) > keyEquivalentWidth)
			keyEquivalentWidth=iw;	// new maximum
		}
}

- (float) _calcHorizontalPositionOfCellComponents;
{
	float x=horizontalEdgePadding;	// x: layout position - start with left-hand padding
	stateImageOffset=x;				// state image starts here
	if(stateImageWidth > 0)
		x+=stateImageWidth+horizontalEdgePadding;	// include space between state image/title
	imageAndTitleOffset=x;			// image&title starts here
	x+=imageAndTitleWidth;			// already includes padding between image and title
	if(keyEquivalentWidth > 0)
		x+=horizontalEdgePadding;	// additional space between image/title and key equivalent
	keyEquivalentOffset=x;			// key equivalents start here
	x+=keyEquivalentWidth;			// total cell width ends here after keyEquivalent
	return x+horizontalEdgePadding;	// add right-hand padding
}

- (void) sizeToFit;
{ // calculate new size and positon of cells - handle horizontal vs. vertical - handle rightToLeft
	// FIXME: handle scrolling of large menus
	NSRect p;				// cell position
	NSRect f;				// frame size
	int i;
	int nc;
	if(!needsSizing)
		return;
#if 0
	NSLog(@"sizeToFit %@", self);
#endif
	if(!window)
		{
#if 1
		NSLog(@"  menu %@ sizeToFit has no window yet", [menu title]);
#endif
		return;	// no reference frame (yet)
		}
#if 1	// workaround to let string drawing additions ask the server for font size (used by NSMenuItemCell)
	[self lockFocus];
#endif
	needsSizing=NO;	// will have been done when calling other methods (avoid endless recursion)
	f=[window frame];	// get enclosing window frame
#if 0
	NSLog(@"window: %@", window);
	NSLog(@"frame before: %@", NSStringFromRect(f));
#endif
	nc=[cells count];
	if(isHorizontal)
		{ // horizontal menu
		imageAndTitleWidth=0.0;	// we don't know for a horizontal menu
		keyEquivalentWidth=0.0;
		stateImageWidth=0.0;
		keyEquivalentOffset=imageAndTitleOffset=stateImageOffset=horizontalEdgePadding;
		p.origin=NSMakePoint(horizontalEdgePadding, 0.0);	// initial position
		for(i=0; i<nc; i++)
			{ // determine element positions
			NSMenuItemCell *c=[cells objectAtIndex:i];
			[c setNeedsSizing:YES];			// get latest values (which might change dynamically if the representedObject supports the -length method)
			p.size=[c cellSize];			// get cell size (based on new total state/image widths)
			// fixme: this should already be returned by [c cellSize] - but self might not be initialized properly yet!
			p.size.height=f.size.height;	// enforce full menu bar height
#if 0
			NSLog(@"item %d width=%lf", i, p.size.width);
#endif
			rectOfCells[i]=p;				// set new cell rectangle
			if(isStatusBar)
				p.origin.x-=p.size.width;	// move left one cell
			else
				p.origin.x+=p.size.width;	// move right one cell
			}
		if(isResizingHorizontally)
			f.size.width=fabs(p.origin.x);		// resize to total width of menu
		if(isStatusBar)
			{ // flush whole status menu to right end
			for(i=0; i<nc; i++)
				{ // determine modified element positions - and redraw full status bar
				rectOfCells[i].origin.x+=f.size.width-horizontalEdgePadding-rectOfCells[i].size.width; // p.origin.x is negative of total menu width
				// [self setNeedsDisplayForItemAtIndex:i];	// and we need to redraw this cell at its new position
				}
			}	
		}
	else
		{ // vertical menu
		p.origin=NSMakePoint(0.0, VERTICAL_PADDING);	// initial position
		[self _calcMaxWidthOfCellComponents];	// get maximum width
		f.size.width=[self _calcHorizontalPositionOfCellComponents];	// calculate standard total width
#if 0
		NSLog(@"si:%lf-%lf i&t:%lf-%lf ke:%lf-%lf width:%lf", stateImageOffset, stateImageWidth, imageAndTitleOffset, imageAndTitleWidth, keyEquivalentOffset, keyEquivalentWidth, f.size.width);
#endif		
		for(i=0; i<nc; i++)
			{ // determine element positions
			NSMenuItemCell *c;
			int ii=nc-i-1;	// reverse order because coordinates are flipped for vertical menus
			c=[cells objectAtIndex:ii];
			p.size=[c cellSize];			// get cell size (based on new total state/image widths)
			p.size.width=f.size.width;				// extend cell width to total menu width
			/// FIXME: we should adjust p already here for the statusBar so that we can reduce redisplay for changed cells
			/// but how should we do that before we know the total width of the status bar???
			//		if(!NSEqualRects(rectOfCells[ii], p))
			//			{ // cell has been moved/resized
			rectOfCells[ii]=p;	// set new cell rectangle
			//			[self setNeedsDisplayForItemAtIndex:ii];	// and we need to redraw this cell
			//			}
			p.origin.y+=p.size.height;	// move down one cell
			}
		f.size.height=p.origin.y+VERTICAL_PADDING;	// resize to total height
		}
#if 1	// workaround to let string drawing additions ask the server for font size (used by NSMenuItemCell)
	[self unlockFocus];
#endif
#if 0
	NSLog(@"NSMenuView sizetofit: window frame=%@", NSStringFromRect(f));
#endif
	[window setFrame:f display:NO];	// resize enclosing window - but do not display immediately; since we are the contenView, we are resized accordingly
	[self setNeedsDisplayInRect:bounds];	// we finally need to redraw full menu, i.e. all items
#if 0
	NSLog(@"sizetofit: done");
#endif
	if(needsSizing)	NSLog(@"NSMenuView sizeToFit: internal inconsistency - did set needsSizing");
}

- (float) stateImageOffset; { if(needsSizing) [self sizeToFit]; return stateImageOffset; }

- (float) stateImageWidth; { if(needsSizing) [self sizeToFit]; return stateImageWidth; }

- (BOOL) trackWithEvent:(NSEvent *) event;
{
	NSPoint p;
	if(attachedMenuView && [attachedMenuView trackWithEvent:event])
		return YES;	// yes, it has been successfully handled by the submenu
	p=[self convertPoint:[window mouseLocationOutsideOfEventStream] fromView:nil];	// get coordinates relative to our window (we might have a different one as the event!)
	if(NSMouseInRect(p, bounds, [self isFlipped]))
		{ // highlight cell
		int item=[self indexOfItemAtPoint:p];	// get selected item
#if 0
		NSLog(@"item=%d", item);
#endif
		if(item != [self highlightedItemIndex])
			{ // has changed
			[self setHighlightedItemIndex:item];	// highligt new item (which will initiate redisplay)
			if(item >= 0 && [[menu itemAtIndex:item] hasSubmenu])
				[self attachSubmenuForItemAtIndex:item];	// and open submenu if available
			else
				[self detachSubmenu];						// detach any open submenu hierarchy
			}
		return YES;
		}
	[self setHighlightedItemIndex:-1];	// unhighligt item if we leave the menu
	return NO;
}

- (void) update;
{ // update autoenabling status and sizes - called once per runloop
#if 0
	NSLog(@"NSMenuView update");
#endif
	[menu update];	// update our menu (and submenus)
	if(needsSizing)
		[self sizeToFit];	// will set needsDisplay
}

// overridden methods

// - (BOOL) isFlipped; { return NO; }

- (BOOL) mouseDownCanMoveWindow; { return NO; }	// no click-move

- (BOOL) isOpaque; { return YES; }	// completely fills its background

- (void) dealloc;
{
#if 0
	NSLog(@"dealloc %@", self);
#endif
	[self detachSubmenu];	// detach any submenu
	[font release];
	[cells release];
	cells=nil;	// no longer available
	[super dealloc];		// will itself call setMenu:nil !!!
}

- (void) drawRect:(NSRect) rect
{ // Drawing code here.
  // FIXME: handle scrolling if too large for screen!
	int i;
	int nc=[cells count];
	if(needsSizing)
		NSLog(@"NSMenuView drawRect: please call sizeToFit explicitly before calling display");	// rect is most probably inaccurate
#if 0
	NSLog(@"NSMenuView - %@ (nc=%d) drawRect:%@", [[menu itemAtIndex:0] title], nc, NSStringFromRect(rect));
#endif
	//// FIXME: the following code deletes all menu items in the drawing rectangle which may be the union of 2 non-adjacent cells!
	//// so this greys out the cells in between...
	[[NSColor windowBackgroundColor] set];	// draw white/light grey lines
	NSRectFill(rect);	// draw background
#if 0	// draw box around menu for testing
	if(!isHorizontal)
		{ // draw box
		[[NSColor brownColor] set];
		// shouldn't this be frame/bounds clipped by rect???
		// everything else could generate artefacts
		NSFrameRect(rect);
		}
#endif
#if 0
	NSLog(@"background filled");
#endif
	for(i=0; i<nc; i++)
		{ // go through cells and draw them at their calculated position - if needed (needsDisplay of cell)
		NSRect cRect=[self rectOfItemAtIndex:i];	// get cell rectangle
#if 0
		NSLog(@"menu=%@", menu);
		NSLog(@"menuitem=%@", [menu itemAtIndex:0]);
		NSLog(@"menuitem title=%@", [[menu itemAtIndex:0] title]);
		NSLog(@"%@ cell:%@%@", [[menu itemAtIndex:0] title], NSStringFromRect(cRect), NSIntersectsRect(rect, cRect)?@" intersects":@"");
#endif
		// FIXME: check needsDisplay - the following code enforces all cells to display!
		if(NSIntersectsRect(cRect, rect))
			{
			NSMenuItemCell *cell=[cells objectAtIndex:i];
			[cell setNeedsDisplay:YES];	// so that we really (re)draw...
			[cell drawInteriorWithFrame:cRect inView:self];
			}
		}
}

- (NSString *) description;
{
	return [NSString stringWithFormat:@"%@ menu:%@ item[0]:%@ %@", 
		NSStringFromClass([self class]), 
		menu,
		[menu numberOfItems] > 0?[[menu itemAtIndex:0] title]:@"?", 
		menu
		];
}

- (BOOL) acceptsFirstMouse:(NSEvent *)theEvent { return YES; } // yes, respond immediately on activation

- (void) mouseDown:(NSEvent *) theEvent;
{ // is not an NSControl so we must track ourselves
	NSTimeInterval menuOpenTimestamp;
	NSMenuView *mv;
	int idx;
	BOOL stillOpen;
#if 1
	NSLog(@"mouseDown:%@ current=%@", theEvent, [[_currentlyOpenMenuView menu] title]);
#endif
	[menu update];	// update/enable menu(s)
	if((stillOpen=(_currentlyOpenMenuView == (NSMenuView *) self)))
		{ // we are still open
		NSLog(@"still open");
		}
	else if(_currentlyOpenMenuView != nil)
		{
		// might have been left open!
		[_currentlyOpenMenuView detachSubmenu];	// close any other still open menu
		}
	else
		_currentlyOpenMenuView=(NSMenuView *) self;	// we are now open
	menuOpenTimestamp=[theEvent timestamp];
	while([theEvent type] != NSLeftMouseUp)
		{ // loop until mouse goes up
		[self trackWithEvent:theEvent];
		theEvent = [NSApp nextEventMatchingMask:GSTrackingLoopMask
									  untilDate:[NSDate distantFuture]			// get next event
										 inMode:NSEventTrackingRunLoopMode 
										dequeue:YES];
		}
	mv=self;
	while([mv attachedMenuView])
		mv=[mv attachedMenuView];	// go down to lawest open submenu level
	if((idx=[mv highlightedItemIndex]) >= 0)
		{ // selected item
		[self setHighlightedItemIndex:-1];	// unhighligt my item
		[isa _deactivate];	// close all submenus
		if(isContextMenu)
			{
#if 1
			NSLog(@"close context menu");
#endif
			[window close];	// NB: this should indirectly delloc self...
			}
		[[mv menu] performActionForItemAtIndex:idx];	// finally perform action - processes responder chain
		return;
		}
	if(stillOpen || [theEvent timestamp]-menuOpenTimestamp > 0.5)
		{ // did not select anything
		[self setHighlightedItemIndex:-1];	// unhighligt my item
		[isa _deactivate];	// close
		}
#if 1
	else
		NSLog(@"stay open");
#endif
}

- (BOOL) shouldBeTreatedAsInkEvent:(NSEvent *) theEvent; { return NO; }	// no inking in menus...

@end

@implementation NSMenu (NSPopupContextMenu)

+ (void) popUpContextMenu:(NSMenu *) menu withEvent:(NSEvent *) event forView:(NSView *) view withFont:(NSFont *) font;
{
	// FIXME: run a tracking loop and return on mouse up
	// so we need no special close window mechanism!
	// 
	// get location by current mouse pointer! so that menu starts below and to the right side:
	//	   NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
	NSWindow *win;
	NSMenuView *menuView;
	NSRect r;
#if 1
	NSLog(@"popUpContextMenu %08x", menu);
	NSLog(@"popUpContextMenu %@", [menu title]);
	NSLog(@"popUpContextMenu event %@", event);
	NSLog(@"popUpContextMenu view %@", view);
	NSLog(@"popUpContextMenu font %@", font);
#endif
	if(!menu || !event || !view)
		return;
	[NSMenuView _deactivate];		// close if any other menu is open
	[menu update];					// enable/disable menu items
	menuView=[[[NSMenuView class] alloc] initWithFrame:NSMakeRect(0.0, 0.0, 50.0, 50.0)];	// make new NSMenuView
	[menuView setFont:font];		// set default font
	[menuView setHorizontal:NO];	// make popup menu vertical
	[menuView _setContextMenu:YES];	// close on mouseUp
	[menuView setMenu:menu];		// define to manage selected menu
	win=[[[NSWindow alloc] initWithContentRect:NSMakeRect(50.0, 50.0, 50.0, 50.0)
									 styleMask:NSBorderlessWindowMask
									   backing:NSBackingStoreBuffered
										 defer:YES] retain];	// will be released on close
	[win setLevel:NSSubmenuWindowLevel];
#if 1
	NSLog(@"win=%@", win);
	NSLog(@"autodisplay=%d", [win isAutodisplay]);
#endif
#if 1
	[win setTitle:@"Context Menu"];
#endif
	[[win contentView] addSubview:menuView];	// add to view hiearachy
	/// FIXME: use mouse location of current event to determine menu location
	r=[view convertRect:[view frame] toView:nil];			// convert view's frame
	r.origin=[[view window] convertBaseToScreen:r.origin];	// to screen coordinates
#if 1
	NSLog(@"menu to be attached to %@", NSStringFromRect(r));
#endif
	[menuView setWindowFrameForAttachingToRect:r
									  onScreen:[win screen]
								 preferredEdge:NSMinYEdge	// default: pull down
							 popUpSelectedItem:-1];
	[win orderFront:self];		// make visible
	[menuView mouseDown:event];	// pass event down
}

@end
