/*hdr
 **      FILE:           GuiMenu.cpp
 **      AUTHOR:         Matthew Allen
 **      DATE:           18/7/98
 **      DESCRIPTION:    Gui menu system
 **
 **      Copyright (C) 1998, Matthew Allen
 **              fret@memecode.com
 */

#include <math.h>
#include <stdio.h>
#include <ctype.h>
#include "Lgi.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/Menu.h"
#include "lgi/common/ToolBar.h"

#define DEBUG_INFO		0

@interface LNSMenuItem : NSMenuItem
{
}
@property LMenuItem* item;
- (id)init:(LMenuItem*)it;
- (void)activate;
- (BOOL)worksWhenModal;
@end

@implementation LNSMenuItem

- (id)init:(LMenuItem*)it
{
	if ((self = [super init]) != nil)
	{
		self.item = it;
		[self setTarget:self];
		self.action = @selector(activate);
	}
	
	return self;
}

- (BOOL)worksWhenModal
{
	return YES;
}

- (void)activate
{
	self.item->OnActivate(self.item);
}

@end

struct LShortcut
{
	NSString *Str;

public:
	NSString *Key;
	NSEventModifierFlags Mod;
	
	LShortcut(const char *s)
	{
		Key = @"";
		Str = nil;
		Mod = 0;

		auto Keys = LString(s).SplitDelimit("+-");
		if (Keys.Length() <= 0)
			return;
		
		for (auto k: Keys)
		{
			if (stricmp(k, "CtrlCmd") == 0 ||
				stricmp(k, "AltCmd") == 0 ||
				stricmp(k, "Cmd") == 0 ||
				stricmp(k, "Command") == 0)
			{
				Mod |= NSEventModifierFlagCommand;
			}
			else if (stricmp(k, "Ctrl") == 0 ||
					stricmp(k, "Control") == 0)
			{
				Mod |= NSEventModifierFlagControl;
			}
			else if (stricmp(k, "Alt") == 0 ||
					stricmp(k, "Option") == 0)
			{
				Mod |= NSEventModifierFlagOption;
			}
			else if (stricmp(k, "Shift") == 0)
			{
				Mod |= NSEventModifierFlagShift;
			}
			else if (stricmp(k, "Del") == 0 ||
					 stricmp(k, "Delete") == 0)
			{
				unichar s[] = {NSDeleteCharacter};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "Ins") == 0 ||
					 stricmp(k, "Insert") == 0)
			{
				unichar s[] = {NSInsertFunctionKey};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "Home") == 0)
			{
				unichar s[] = {NSHomeFunctionKey};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "End") == 0)
			{
				unichar s[] = {NSEndFunctionKey};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "PageUp") == 0)
			{
				unichar s[] = {NSPageUpFunctionKey};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "PageDown") == 0)
			{
				unichar s[] = {NSPageDownFunctionKey};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "Backspace") == 0)
			{
				unichar s[] = {NSBackspaceCharacter};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (stricmp(k, "Space") == 0)
			{
				Key = @" ";
			}
			else if (k[0] == 'F' && isdigit(k[1]))
			{
				int64 index = k.Strip("F").Int();
				unichar s[] = {(unichar)(NSF1FunctionKey + index - 1)};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else if (isalpha(k[0]))
			{
				Key = Str = LString(k).Lower().NsStr();
			}
			else if (isdigit(k[0]))
			{
				Key = Str = LString(k).NsStr();
			}
			else if (strchr(",.", k(0)))
			{
				unichar s[] = {(unichar)k(0)};
				Key = Str = [[NSString alloc] initWithCharacters:s length:1];
			}
			else
			{
				printf("%s:%i - Unhandled shortcut token '%s'\n", _FL, k.Get());
			}
		}
	}
	
	~LShortcut()
	{
		if (Str) [Str release];
	}
};

///////////////////////////////////////////////////////////////////////////////////////////////
LSubMenu::LSubMenu(const char *name, bool Popup)
{
	Menu = 0;
	Parent = 0;
	Info = NULL;
	LBase::Name(name);
	Info.p = [[NSMenu alloc] init];
	[Info.p setAutoenablesItems:NO];
}

LSubMenu::~LSubMenu()
{
	while (Items.Length())
	{
		LMenuItem *i = Items[0];
		if (i->Parent != this)
		{
			i->Parent = NULL;
			Items.Delete(i);
		}
		delete i;
	}
	
	if (Info)
	{
		[Info.p release];
		Info = NULL;
	}
}

void LSubMenu::OnAttach(bool Attach)
{
	for (auto i: Items)
	{
		i->OnAttach(Attach);
	}
	
	if (Attach &&
		this != Menu &&
		Parent &&
		Parent->Parent)
	{
	}
}

size_t LSubMenu::Length()
{
	return Items.Length();
}

LMenuItem *LSubMenu::ItemAt(int Id)
{
	return Items.ItemAt(Id);
}

LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut)
{
	LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where, Shortcut);
	if (!i || !Info)
		return NULL;

	Items.Insert(i, Where);
	auto Index = Items.IndexOf(i);
	// auto Max = Info.p.numberOfItems;

	LString s(i->LBase::Name());
	auto name = s.NsStr();
	if (!name)
	{
		delete i;
		return NULL;
	}
	
	LShortcut sc(Shortcut);

	i->Info.p = [[LNSMenuItem alloc] init:i];
	if (!i->Info)
	{
		Items.Delete(i);
		delete i;
		return NULL;
	}

	[i->Info.p setTitle:name];
	i->Info.p.keyEquivalent = sc.Key;
	i->Info.p.keyEquivalentModifierMask = sc.Mod;
	i->Id(Id);
	i->Enabled(Enabled);

	[Info.p insertItem:i->Info atIndex:Index];

	return i;
}

LMenuItem *LSubMenu::AppendSeparator(int Where)
{
	LMenuItem *i = new LMenuItem;
	if (i)
	{
		i->Parent = this;
		i->Menu = Menu;
		i->Id(-2);
		
		Items.Insert(i, Where);
		
		if (Info)
		{
			auto Index = Items.IndexOf(i);
			// auto Max = Info.p.numberOfItems;
			// printf("Adding ----- @ %i, %i\n", (int)Index, (int)Max);

			i->Info = [NSMenuItem separatorItem];
			[Info.p insertItem:i->Info atIndex:Index];
		}
		else
		{
			printf("%s:%i - No menu to attach item to.\n", _FL);
		}
		
		return i;
	}
	
	return 0;
}

LSubMenu *LSubMenu::AppendSub(const char *Str, int Where)
{
	LMenuItem *i = new LMenuItem;
	if (i && Str)
	{
		i->Parent = this;
		i->Menu = Menu;
		i->Id(-1);
		
		Items.Insert(i, Where);
		if (Info)
		{
			i->Child = new LSubMenu(Str);
			if (i->Child)
			{
				i->Child->Parent = i;
				i->Child->Menu = Menu;
				i->Child->Window = Window;
				
				i->Info.p = [[NSMenuItem alloc] init];
				LAssert(i->Info);

				i->Name(Str);
				LString s(i->LBase::Name());
				[i->Child->Info.p setTitle:s.NsStr()];
				[i->Info.p setSubmenu:i->Child->Info.p];
				
				auto Index = Items.IndexOf(i);
				auto IsMenu = dynamic_cast<LMenu*>(this);
				auto Offset = IsMenu && Where >= 0 ? 1 : 0; // Adjust for 'Root' element "app menu"
				[Info.p insertItem:i->Info atIndex:Index + Offset];
			}
		}
		else
		{
			printf("%s:%i - No menu to attach item to.\n", __FILE__, __LINE__);
		}
		
		return i->Child;
	}
	
	return 0;
}

void LSubMenu::Empty()
{
	while (Items[0])
	{
		if (!RemoveItem(Items[0]))
			break; // Otherwise we'll get an infinite loop.
	}
}

bool LSubMenu::RemoveItem(int i)
{
	LMenuItem *Item = Items[i];
	if (Item)
	{
		return Item->Remove();
	}
	return false;
}

bool LSubMenu::RemoveItem(LMenuItem *Item)
{
	if (Item &&
		Items.HasItem(Item))
	{
		return Item->Remove();
	}
	return false;
}

bool LSubMenu::OnKey(LKey &k)
{
	return false;
}

void LSubMenu::OnActivate(LMenuItem *item)
{
	if (!item)
		return;
	
	if (FloatResult)
		*FloatResult = item->Id();
	else if (Parent)
		Parent->OnActivate(item);
	else
		LAssert(!"Should have a float result OR a parent..");
}

int LSubMenu::Float(LView *From, int x, int y, int Btns)
{
	LPoint p(x, y);
	OsView v = nil;
	
	if (From)
		From->Capture(false);

	auto w = From ? From->GetWindow() : NULL;
	if (w)
	{
		v = w->Handle();
		w->PointToView(p);
		p = w->Flip(p);
	}
	
	FloatResult.Reset(new int(0));
	
	// auto item = Items[0];
	// auto menuitem = item->Info.p;
	// auto en = menuitem.enabled;
	
	NSPoint loc = {(double)p.x, (double)p.y};
	[Info.p popUpMenuPositioningItem:nil atLocation:loc inView:v];

	return FloatResult ? *FloatResult : 0;
}

LSubMenu *LSubMenu::FindSubMenu(int Id)
{
	for (auto i: Items)
	{
		LSubMenu *Sub = i->Sub();
		
		if (i->Id() == Id)
		{
			return Sub;
		}
		else if (Sub)
		{
			LSubMenu *m = Sub->FindSubMenu(Id);
			if (m)
			{
				return m;
			}
		}
	}
	
	return 0;
}

LMenuItem *LSubMenu::FindItem(int Id)
{
	for (auto i: Items)
	{
		LSubMenu *Sub = i->Sub();
		
		if (i->Id() == Id)
		{
			return i;
		}
		else if (Sub)
		{
			i = Sub->FindItem(Id);
			if (i)
			{
				return i;
			}
		}
	}
	
	return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////
class LMenuItemPrivate
{
public:
	LString Shortcut;
};

LMenuItem::LMenuItem()
{
	d = new LMenuItemPrivate();
	Menu = NULL;
	Info = NULL;
	Child = NULL;
	Parent = NULL;
	_Icon = -1;
	_Id = 0;
	_Flags = 0;
}

LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Str, int Id, int Pos, const char *Shortcut)
{
	d = new LMenuItemPrivate();
	LBase::Name(Str);
	Menu = m;
	Parent = p;
	Info = NULL;
	Child = NULL;
	_Icon = -1;
	_Id = Id;
	_Flags = 0;
	d->Shortcut = Shortcut;
	Name(Str);
	ScanForAccel();
}

LMenuItem::~LMenuItem()
{
	if (Parent)
	{
		Parent->Items.Delete(this);
		Parent = NULL;
	}
	DeleteObj(Child);
	DeleteObj(d);
}

void LMenuItem::OnActivate(LMenuItem *item)
{
	if (Parent)
		Parent->OnActivate(item);
	else
		LAssert(!"Should have a parent.");
}

void LMenuItem::OnAttach(bool Attach)
{
	if (Attach)
	{
		if (_Icon >= 0)
		{
			Icon(_Icon);
		}
		
		if (Sub())
		{
			Sub()->OnAttach(Attach);
		}
	}
}

// the following 3 functions paint the menus according the to
// windows standard. but also allow for correct drawing of menuitem
// icons. some implementations of windows force the program back
// to the 8-bit palette when specifying the icon graphic, thus removing
// control over the colours displayed. these functions remove that
// limitation and also provide the application the ability to override
// the default painting behaviour if desired.
void LMenuItem::_Measure(LPoint &Size)
{
	auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
	bool BaseMenu = Parent == Menu; // true if attached to a windows menu
	// else is a submenu
	int Ht = Font->GetHeight();
	// int IconX = BaseMenu ? ((24-Ht)/2)-Ht : 20;
	int IconX = BaseMenu ? 2 : 16;
	
	if (Separator())
	{
		Size.x = 8;
		Size.y = 8;
	}
	else
	{
		// remove '&' chars for string measurement
		char Str[256];
		const char *n = Name(), *i = n;
		char *o = Str;
		
		while (i && *i)
		{
			if (*i == '&')
			{
				if (i[1] == '&')
				{
					*o++ = *i++;
				}
			}
			else
			{
				*o++ = *i;
			}
			
			i++;
		}
		*o++ = 0;
		
		// check for accelerators
		char *Tab = strchr(Str, '\t');
		if (Tab)
		{
			// string with accel
			int Mx, Tx;
			LDisplayString ds(Font, Str, Tab-Str);
			Mx = ds.X();
			LDisplayString ds2(Font, Tab + 1);
			Tx = ds2.X();
			Size.x = IconX + 32 + Mx + Tx;
		}
		else
		{
			// normal string
			LDisplayString ds(Font, Str);
			Size.x = IconX + ds.X() + 4;
		}
		
		if (!BaseMenu)
		{
			// leave room for child pointer
			Size.x += Child ? 8 : 0;
		}
		
		Size.y = MAX(IconX, Ht+2);
	}
}

#define Time(a, b) ((double)(b - a) / 1000)

void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width)
{
	auto n = Name();
	if (n)
	{
		auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
		bool Underline = false;
		const char *e = 0;
		for (auto s=n; s && *s; s = *e ? e : 0)
		{
			switch (*s)
			{
				case '&':
				{
					if (s[1] == '&')
					{
						e = s + 2;
						LDisplayString d(Font, "&");
						d.Draw(pDC, x, y, 0);
						x += d.X();
					}
					else
					{
						Underline = true;
						e = s + 1;
					}
					break;
				}
				case '\t':
				{
					LDisplayString ds(Font, e + 1);
					x = Width - ds.X() - 8;
					e = s + 1;
					break;
				}
				default:
				{
					if (Underline)
					{
						LgiNextUtf8(e);
					}
					else
					{
						for (e = s; *e; e++)
						{
							if (*e == '\t') break;
							if (*e == '&') break;
						}
					}
					
					ptrdiff_t Len = e - s;
					if (Len > 0)
					{
						// paint text till that point
						LDisplayString d(Font, s, Len);
						d.Draw(pDC, x, y, 0);
						
						if (Underline)
						{
							LDisplayString ds(Font, s, 1);
							int UnderX = ds.X();
							int Ascent = (int)ceil(Font->Ascent());
							pDC->Colour(Font->Fore());
							pDC->Line(x, y+Ascent+1, x+MAX(UnderX-2, 1), y+Ascent+1);
							Underline = false;
						}
						
						x += d.X();
					}
					break;
				}
			}
		}
	}
	
}

void LMenuItem::_Paint(LSurface *pDC, int Flags)
{
	bool BaseMenu = Parent == Menu;
	int IconX = BaseMenu ? 5 : 20;
	bool Selected = TestFlag(Flags, ODS_SELECTED);
	bool Disabled = TestFlag(Flags, ODS_DISABLED);
	bool Checked = TestFlag(Flags, ODS_CHECKED);
	
#if defined(WIN32) || defined(MAC)
	LRect r(0, 0, pDC->X()-1, pDC->Y()-1);
#else
	LRect r = Info->GetClient();
#endif
	
	if (Separator())
	{
		// Paint a separator
		int Cy = r.Y() / 2;
		
		pDC->Colour(L_MED);
		pDC->Rectangle();
		
		pDC->Colour(L_LOW);
		pDC->Line(0, Cy-1, pDC->X()-1, Cy-1);
		
		pDC->Colour(L_LIGHT);
		pDC->Line(0, Cy, pDC->X()-1, Cy);
	}
	else
	{
		// Paint a text menu item
		LColour Fore(L_TEXT);
		LColour Back(Selected ? L_HIGH : L_MED);
		int x = IconX;
		int y = 1;
		
		// For a submenu
		pDC->Colour(Back);
		pDC->Rectangle();
		
		// Draw the text on top
		LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
		Font->Transparent(true);
		if (Disabled)
		{
			// Disabled text
			if (!Selected)
			{
				Font->Colour(L_LIGHT);
				_PaintText(pDC, x+1, y+1, r.X());
			}
			// Else selected... don't draw the hilight
			
			// "greyed" text...
			Font->Colour(L_LOW);
			_PaintText(pDC, x, y, r.X());
		}
		else
		{
			// Normal coloured text
			Font->Fore(Fore);
			_PaintText(pDC, x, y, r.X());
		}
		
		auto ImgLst = (Menu && Menu->GetImageList()) ? Menu->GetImageList() : Parent ? Parent->GetImageList() : 0;
		
		// Draw icon/check mark
		if (Checked && IconX > 0)
		{
			// it's a check!
			int x = 4;
			int y = 6;
			
			pDC->Colour(Fore);
			pDC->Line(x, y, x+2, y+2);
			pDC->Line(x+2, y+2, x+6, y-2);
			y++;
			pDC->Line(x, y, x+2, y+2);
			pDC->Line(x+2, y+2, x+6, y-2);
			y++;
			pDC->Line(x, y, x+2, y+2);
			pDC->Line(x+2, y+2, x+6, y-2);
		}
		else if (ImgLst &&
				 _Icon >= 0)
		{
			// it's an icon!
			LColour Bk(L_MED);
			ImgLst->Draw(pDC, 0, 0, _Icon, Bk);
		}
		
		// Sub menu arrow
		if (Child && !dynamic_cast<LMenu*>(Parent))
		{
			pDC->Colour(L_TEXT);
			
			int x = r.x2 - 4;
			int y = r.y1 + (r.Y()/2);
			for (int i=0; i<4; i++)
			{
				pDC->Line(x, y-i, x, y+i);
				x--;
			}
		}
	}
}

bool LMenuItem::ScanForAccel()
{
	if (!d->Shortcut)
		return false;
	
	// printf("d->Shortcut=%s\n", d->Shortcut.Get());
	auto Keys = d->Shortcut.SplitDelimit("+-");
	if (Keys.Length() > 0)
	{
		int Flags = 0;
		char16 Key = 0;
		
		for (int i=0; i<Keys.Length(); i++)
		{
			const char *k = Keys[i];

			if (!stricmp(k, "CtrlCmd"))
				Flags |= LGI_EF_SYSTEM;
			else if (!stricmp(k, "AltCmd"))
				Flags |= LGI_EF_SYSTEM;
			else if (stricmp(k, "Ctrl") == 0)
				Flags |= LGI_EF_CTRL;
			else if (stricmp(k, "Alt") == 0)
				Flags |= LGI_EF_ALT;
			else if (stricmp(k, "Shift") == 0)
				Flags |= LGI_EF_SHIFT;
			else if (stricmp(k, "Del") == 0 ||
					 stricmp(k, "Delete") == 0)
				Key = LK_DELETE;
			else if (stricmp(k, "Ins") == 0 ||
					 stricmp(k, "Insert") == 0)
				Key = LK_INSERT;
			else if (stricmp(k, "Home") == 0)
				Key = LK_HOME;
			else if (stricmp(k, "End") == 0)
				Key = LK_END;
			else if (stricmp(k, "PageUp") == 0)
				Key = LK_PAGEUP;
			else if (stricmp(k, "PageDown") == 0)
				Key = LK_PAGEDOWN;
			else if (stricmp(k, "Backspace") == 0)
				Key = LK_BACKSPACE;
			else if (stricmp(k, "Left") == 0)
				Key = LK_LEFT;
			else if (stricmp(k, "Up") == 0)
				Key = LK_UP;
			else if (stricmp(k, "Right") == 0)
				Key = LK_RIGHT;
			else if (stricmp(k, "Down") == 0)
				Key = LK_DOWN;
			else if (!stricmp(k, "Esc") || !stricmp(k, "Escape"))
				Key = LK_ESCAPE;
			else if (stricmp(k, "Space") == 0)
				Key = ' ';
			else if (k[0] == 'F' && isdigit(k[1]))
			{
				int Idx = atoi(k+1);
				Key = LK_F1 + Idx - 1;
			}
			else if (isalpha(k[0]))
				Key = toupper(k[0]);
			else if (isdigit(k[0]) || strchr(",", k[0]))
				Key = k[0];
			else
				LgiTrace("%s:%i - Unknown part '%s' in shortcut '%s'\n", _FL, k, d->Shortcut.Get());
		}
		
		if (Key)
		{
			if ((Flags & LGI_EF_ALT) != 0 &&
				(Flags & LGI_EF_SYSTEM) == 0)
			{
				auto Ident = Id();
				LAssert(Ident > 0);
				Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) );
			}
		}
		else
		{
			printf("%s:%i - Accel scan failed, str='%s'\n", _FL, d->Shortcut.Get());
			return false;
		}
	}

	
	return true;
}

LSubMenu *LMenuItem::GetParent()
{
	return Parent;
}

bool LMenuItem::Remove()
{
	if (!Parent)
		return false;

	if (Parent->Info && Info)
	{
		[Parent->Info.p removeItem:Info];
		Parent->Items.Delete(this);
		Info = NULL;
	}
	else
	{
		Parent->Items.Delete(this);
	}
	
	return true;
}

void LMenuItem::Id(int i)
{
	_Id = i;
	if (Parent && Parent->Info && Info)
	{
		#if LGI_COCOA
		#else
		SetMenuItemCommandID(Parent->Info, Info, _Id);
		#endif
	}
}

void LMenuItem::Separator(bool s)
{
	if (s)
	{
		_Id = -2;
	}
	
	if (Parent)
	{
		#if LGI_COCOA
		#else
		if (s)
			ChangeMenuItemAttributes(Parent->Info, Info, kMenuItemAttrSeparator, 0);
		else
			ChangeMenuItemAttributes(Parent->Info, Info, 0, kMenuItemAttrSeparator);
		#endif
	}
}

void LMenuItem::Checked(bool c)
{
	if (c)
		SetFlag(_Flags, ODS_CHECKED);
	else
		ClearFlag(_Flags, ODS_CHECKED);
	
	if (Info)
		[Info.p setState: c ? NSOnState : NSOffState];
}

bool LMenuItem::Name(const char *n)
{
	char *Tmp = NewStr(n);
	if (Tmp)
	{
		char *in = Tmp, *out = Tmp;
		while (*in)
		{
			if (*in != '&')
				*out++ = *in;
			in++;
		}
		*out++ = 0;
	}
	
	bool Status = LBase::Name(Tmp);
	if (Status && Info)
	{
		LString s(Tmp);
		[Info.p setTitle:s.NsStr()];
	}
	
	DeleteArray(Tmp);
	
	return Status;
}

void LMenuItem::Enabled(bool e)
{
	#if 1
	if (Info && Info.p.enabled ^ e)
		Info.p.enabled = e;
	#endif
}

void LMenuItem::Focus(bool f)
{
}

void LMenuItem::Sub(LSubMenu *s)
{
	Child = s;
}

void LMenuItem::Icon(int i)
{
	_Icon = i;
	
	auto Lst = Menu ? Menu->GetImageList() : Parent->GetImageList();
	if (!Lst || !Info)
		return;

	if (_Icon < 0 || _Icon >= Lst->GetItems())
		return;

	auto r = Lst->GetIconRect(_Icon);
	[Info.p setImage: Lst->NsImage(&r)];
}

void LMenuItem::Visible(bool i)
{
}

int LMenuItem::Id()
{
	return _Id;
}

const char *LMenuItem::Name()
{
	return LBase::Name();
}

bool LMenuItem::Separator()
{
	return _Id == -2;
}

bool LMenuItem::Checked()
{
	return TestFlag(_Flags, ODS_CHECKED);
}

bool LMenuItem::Enabled()
{
	if (Parent)
	{
		#if LGI_COCOA
		#else
		return IsMenuItemEnabled(Parent->Info, Info);
		#endif
	}
	
	return true;
}

bool LMenuItem::Visible()
{
	return true;
}

bool LMenuItem::Focus()
{
	return 0;
}

LSubMenu *LMenuItem::Sub()
{
	return Child;
}

int LMenuItem::Icon()
{
	return _Icon;
}

///////////////////////////////////////////////////////////////////////////////////////////////
class LMenuPrivate
{
public:
	int PrefId, AboutId;
	LMenuItem *PrefItem = NULL;
	LMenuItem *AboutItem = NULL;
	
	LMenuPrivate()
	{
		PrefId = AboutId = 0;
	}
};


LMenu::LMenu(const char *AppName) : LSubMenu("", false)
{
	Menu = this;
	d = new LMenuPrivate;
	
	auto s = AppendSub("Root");
	if (s)
	{
		d->AboutItem = s->AppendItem("About", M_ABOUT);
		s->AppendSeparator();
		d->PrefItem = s->AppendItem("Preferences", M_PERFERENCES, true, -1, "Cmd+,");
		s->AppendItem("Hide", M_HIDE, true, -1, "Cmd+H");
		s->AppendSeparator();
		s->AppendItem("Quit", M_QUIT, true, -1, "Cmd+Q");
	}
}

LMenu::~LMenu()
{
	Accel.DeleteObjects();
	DeleteObj(d);
}

void LMenu::OnActivate(LMenuItem *item)
{
	if (!item)
	{
		LAssert(0);
		return;
	}
	switch (item->Id())
	{
		case M_ABOUT:
			if (Window && d->AboutId)
				Window->PostEvent(M_COMMAND, d->AboutId);
			break;
		case M_PERFERENCES:
			if (Window && d->PrefId)
				Window->PostEvent(M_COMMAND, d->PrefId);
			break;
		case M_HIDE:
			[[NSApplication sharedApplication] hide:Info];
			break;
		case M_QUIT:
			LCloseApp();
			break;
		default:
			if (Window)
				Window->PostEvent(M_COMMAND, item->Id());
			break;
	}
}

bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId)
{
	d->PrefId = PrefId;
	if (d->PrefItem)
		d->PrefItem->Enabled(d->PrefId > 0);
	
	d->AboutId = AboutId;
	if (d->AboutItem)
		d->AboutItem->Enabled(d->AboutId > 0);
		
	return true;
}

struct LMenuFont
{
	LFont *f;

	LMenuFont()
	{
		f = NULL;
	}

	~LMenuFont()
	{
		DeleteObj(f);
	}
}	MenuFont;

LFont *LMenu::GetFont()
{
	if (!MenuFont.f)
	{
		LFontType Type;
		if (Type.GetSystemFont("Menu"))
		{
			MenuFont.f = Type.Create();
			if (MenuFont.f)
			{
#ifndef MAC
				_Font->CodePage(SysFont->CodePage());
#endif
			}
			else
			{
				printf("LMenu::GetFont Couldn't create menu font.\n");
			}
		}
		else
		{
			printf("LMenu::GetFont Couldn't get menu typeface.\n");
		}
		
		if (!MenuFont.f)
		{
			MenuFont.f = new LFont;
			if (MenuFont.f)
				*MenuFont.f = *LSysFont;
		}
	}
	
	return MenuFont.f ? MenuFont.f : LSysFont;
}

bool LMenu::Attach(LViewI *p)
{
	bool Status = false;
	
	auto w = dynamic_cast<LWindow*>(p);
	if (w)
	{
		Window = p;
		[NSApplication sharedApplication].mainMenu = Info;
		
		if (Info)
		{
			OnAttach(true);
			Status = true;
		}
		else
		{
			printf("%s:%i - No menu\n", _FL);
		}
	}
	
	return Status;
}

bool LMenu::Detach()
{
	bool Status = false;
	return Status;
}

bool LMenu::OnKey(LView *v, LKey &k)
{
	if (k.Down())
	{
		k.Trace("MenuKey");
		for (auto a: Accel)
		{
			if (a->Match(k))
			{
				Window->OnCommand(a->GetId(), 0, NULL);
				return true;
			}
		}
		
		if (k.Alt() &&
			!dynamic_cast<LMenuItem*>(v) &&
			!dynamic_cast<LSubMenu*>(v))
		{
			bool Hide = false;
			
			for (auto s: Items)
			{
				if (!s->Separator())
				{
					if (Hide)
					{
						// s->Info->HideSub();
					}
					else
					{
						auto n = s->Name();
						if (ValidStr(n))
						{
							char *Amp = strchr(n, '&');
							while (Amp && Amp[1] == '&')
							{
								Amp = strchr(Amp + 2, '&');
							}
							
							if (Amp)
							{
								char Accel = tolower(Amp[1]);
								char Press = tolower(k.c16);
								if (Accel == Press)
								{
									Hide = true;
								}
							}
						}
						
						if (Hide)
						{
							// s->Info->ShowSub();
						}
						else
						{
							// s->Info->HideSub();
						}
					}
				}
			}
			
			if (Hide)
			{
				return true;
			}
		}
	}
	
	return false;
}

////////////////////////////////////////////////////////////////////////////
LAccelerator::LAccelerator(int flags, int key, int id)
{
	Flags = flags;
	Key = key;
	Id = id;
}

bool LAccelerator::Match(LKey &k)
{
	int Press = (uint) k.c16;
	auto Up = toupper(Press);
	bool Match = false;
	
#if 1
	printf("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s\n",
		   Up,
		   Up>=' '?Up:'.',
		   k.Ctrl()?" ctrl":"",
		   k.Alt()?" alt":"",
		   k.Shift()?" shift":"",
		   Key,
		   Key>=' '?Key:'.',
		   TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"",
		   TestFlag(Flags, LGI_EF_ALT)?" alt":"",
		   TestFlag(Flags, LGI_EF_SHIFT)?" shift":""
		   );
#endif
	
	if (k.Alt() && !k.System() && !k.Ctrl())
	{
		switch (k.vkey)
		{
			#define _(k) case LK_##k: \
				Match = Key == #k[0]; break;
			_(A) _(B) _(C) _(D) _(E) _(F) _(G) _(H) _(I) _(J) _(K) _(L) _(M)
			_(N) _(O) _(P) _(Q) _(R) _(S) _(T) _(U) _(V) _(W) _(X) _(Y) _(Z)
			default:
				printf("%s:%i - No case for '%i'\n", _FL, k.vkey);
				break;
		}
	}
	else
	{
		Match = Up == (uint)Key;
	}
	
	
	if (Match)
	{
		if
			(
			 ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0)
			 &&
			 ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0)
			 &&
			 ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0)
			 )
		{
			return true;
		}
	}
	
	return false;
}

////////////////////////////////////////////////////////////////////////////
LCommand::LCommand()
{
	Flags = GWF_VISIBLE;
	Id = 0;
	ToolButton = 0;
	MenuItem = 0;
	Accelerator = 0;
	TipHelp = 0;
	PrevValue = false;
}

LCommand::~LCommand()
{
	DeleteArray(Accelerator);
	DeleteArray(TipHelp);
}

bool LCommand::Enabled()
{
	if (ToolButton)
		return ToolButton->Enabled();
	if (MenuItem)
		return MenuItem->Enabled();
	return false;
}

void LCommand::Enabled(bool e)
{
	if (ToolButton)
	{
		ToolButton->Enabled(e);
	}
	if (MenuItem)
	{
		MenuItem->Enabled(e);
	}
}

bool LCommand::Value()
{
	bool HasChanged = false;
	
	if (ToolButton)
	{
		HasChanged |= (ToolButton->Value() != 0) ^ PrevValue;
	}
	if (MenuItem)
	{
		HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue;
	}
	
	if (HasChanged)
	{
		Value(!PrevValue);
	}
	
	return PrevValue;
}

void LCommand::Value(bool v)
{
	if (ToolButton)
	{
		ToolButton->Value(v);
	}
	if (MenuItem)
	{
		MenuItem->Checked(v);
	}
	PrevValue = v;
}
