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

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "lgi/common/Lgi.h"
#include "lgi/common/Token.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/Menu.h"
#include "lgi/common/ToolBar.h"

///////////////////////////////////////////////////////////////////////////////////////////////
#define LGI_OWNER_DRAW_MENUS

LColour LGetSysColor(int nIndex)
{
	LColour c;
	auto i = GetSysColor(nIndex);
	c.Rgb(GetRValue(i), GetGValue(i), GetBValue(i));
	return c;
}

class LMenuPrivate
{
public:
	LColour RootMenuBack;
	
	LMenuPrivate()
	{
		LArray<int> Ver;
		int Os = LGetOs(&Ver);
		if
		(
			(
				Os == LGI_OS_WIN32
				||
				Os == LGI_OS_WIN64
			)		
			&&
			(
				Ver[0] > 5
				||
				(Ver[0] == 5 && Ver[1] > 0)
			)
		)
		{
			#ifndef SPI_GETFLATMENU
			#define SPI_GETFLATMENU 0x1022
			#endif

			BOOL Flat = true;
			SystemParametersInfo(SPI_GETFLATMENU, 0, &Flat, 0);
			if (Flat)
				RootMenuBack = LGetSysColor(COLOR_MENUBAR);
			else
				RootMenuBack = LGetSysColor(COLOR_MENU);
			// LgiTrace("NT: flat=%i, menu=%x\n", Flat, RootMenuBack);
		}
		else
		{
			RootMenuBack = LGetSysColor(COLOR_MENU);
			// LgiTrace("9x: menu=%x\n", RootMenuBack);
		}
	}
};

///////////////////////////////////////////////////////////////////////////////////////////////
LSubMenu::LSubMenu(const char *name, bool Popup)
{
	if (Popup)
	{
		Info = CreatePopupMenu();
	}
	else
	{
		Info = CreateMenu();
	}

	Menu = 0;
	Parent = 0;
	TrackHandle = 0;
	Window = 0;
}

LSubMenu::~LSubMenu()
{
	if (TrackHandle)
	{
		// At least in Win98, if you call InvalidateRect(...) on a window
		// multiple times just after you use TracePopupMenu(...) on that
		// window the invalid region isn't updated.
		//
		// This behaviour is not by design, it is a bug. However this
		// update forces the window to repaint thus working around the
		// bug in windows.
		UpdateWindow(TrackHandle);
		TrackHandle = 0;
	}

	if (Info)
	{
		DestroyMenu(Info);
		Info = 0;
	}

	if (Parent)
	{
		Parent->Child = 0;
	}
	
	LMenuItem *i;
	while (i = Items[0])
	{
		LAssert(i->Parent == this);
		DeleteObj(i);
	}
}

void LSubMenu::SysMouseClick(LMouse &m)
{
}

LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut)
{
	int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length()));

	LMenuItem *Item = new LMenuItem(Menu, this, Str, Id, Pos, Shortcut);
	if (Item)
	{
		Item->Enabled(Enabled);
		Items.Insert(Item, Where);
		Item->ScanForAccel();
	}

	if (Menu)
	{
		Menu->OnChange();
	}

	return Item;
}

LMenuItem *LSubMenu::AppendSeparator(int Where)
{
	LMenuItem *Item = new LMenuItem;
	if (Item)
	{
		Item->Menu = Menu;
		Items.Insert(Item, Where);
		Item->Position = (int) Items.IndexOf(Item);
		Item->Separator(true);
		Item->Parent = this;
		Item->Insert(Item->Position);
	}

	if (Menu)
	{
		Menu->OnChange();
	}

	return Item;
}

LSubMenu *LSubMenu::AppendSub(const char *Str, int Where)
{
	int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length()));
	LMenuItem *Item = new LMenuItem(Menu, this, Str, -1, Pos);
	LSubMenu *Sub = new LSubMenu;

	if (Item && Sub)
	{
		Items.Insert(Item, Where);
		Item->Sub(Sub);
	}
	else
	{
		DeleteObj(Item);
		DeleteObj(Sub);
	}

	if (Menu)
	{
		Menu->OnChange();
	}

	return Sub;
}

void LSubMenu::Empty()
{
	while (RemoveItem(0));
}

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

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

int LSubMenu::Float(LView *From, int x, int y, int Button)
{
	int Cmd = 0;

	if (From && Info)
	{
		LViewI *Wnd = From;
		while (Wnd && !Wnd->Handle())
		{
			Wnd = Wnd->GetParent();
		}

		if (Wnd && Wnd->Handle())
		{
			Cmd = TrackPopupMenu(	Info,
									TPM_LEFTALIGN |
									TPM_TOPALIGN |
									TPM_RETURNCMD |
									((Button == BtnLeft) ? TPM_LEFTBUTTON : TPM_RIGHTBUTTON),
									x,
									y,
									0,
									Wnd->Handle(),
									NULL);
		}
		else
		{
			LgiTrace("%s:%i - No handle to track popup menu.\n", __FILE__, __LINE__);
		}
	}

	return Cmd;
}

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

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

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:
	bool StartUnderline;		// Underline the first display string
	bool HasAccel;				// The last display string should be right aligned
	List<LDisplayString> Strs;	// Draw each alternate display string with underline
								// except the last in the case of HasAccel==true.
	LString Shortcut;

	LMenuItemPrivate()
	{
		HasAccel = false;
		StartUnderline = false;
	}

	~LMenuItemPrivate()
	{
		Strs.DeleteObjects();
	}

	void UpdateStrings(LFont *Font, char *n)
	{
		// Build up our display strings, 
		Strs.DeleteObjects();
		StartUnderline = false;

		char *Tab = strrchr(n, '\t');
		if (Tab)
		{
			*Tab = 0;
		}

		char *Amp = 0, *a = n;
		while (a = strchr(a, '&'))
		{
			if (a[1] != '&')
			{
				Amp = a;
				break;
			}

			a++;
		}

		if (Amp)
		{
			// Before amp
			Strs.Insert(new LDisplayString(Font, n, Amp - n ));

			// Amp'd letter
			char *e = LSeekUtf8(++Amp, 1);
			Strs.Insert(new LDisplayString(Font, Amp, e - Amp ));

			// After Amp
			if (*e)
			{
				Strs.Insert(new LDisplayString(Font, e));
			}
		}
		else
		{
			Strs.Insert(new LDisplayString(Font, n));
		}

		if (HasAccel = (Tab != 0))
		{
			Strs.Insert(new LDisplayString(Font, Tab + 1));
			*Tab = '\t';
		}
		else if (HasAccel = (Shortcut.Get() != 0))
		{
			Strs.Insert(new LDisplayString(Font, Shortcut));
		}
	}
};

LMenuItem::LMenuItem()
{
	d = new LMenuItemPrivate;
	Menu = NULL;
	Parent = NULL;
	Child = 0;
	Position = -1;
	_Icon = -1;

	ZeroObj(Info);
	Info.cbSize = sizeof(Info);

	#ifdef LGI_OWNER_DRAW_MENUS
	Info.fMask = MIIM_DATA;
	Info.fType = MFT_OWNERDRAW;

	#if _MSC_VER >= _MSC_VER_VS2005
	Info.dwItemData = (ULONG_PTR)this;
	#else
	Info.dwItemData = (DWORD)this;
	#endif

	#endif

	Enabled(true);
}

LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Txt, int id, int Pos, const char *Shortcut)
{
	d = new LMenuItemPrivate;
	d->Shortcut = Shortcut;
	Position = Pos;
	Parent = NULL;
	Menu = m;
	Parent = NULL;
	Child = 0;
	_Icon = -1;

	ZeroObj(Info);
	Info.cbSize = sizeof(Info);

	#ifdef LGI_OWNER_DRAW_MENUS
	Info.fMask = MIIM_DATA;
	Info.fType = MFT_OWNERDRAW;
	#if _MSC_VER >= _MSC_VER_VS2005
	Info.dwItemData = (ULONG_PTR)this;
	#else
	Info.dwItemData = (DWORD)this;
	#endif
	#endif

	Id(id);
	Enabled(true);
	Name(Txt);

	Parent = p;
	Insert(Position);
}

LMenuItem::~LMenuItem()
{
	if (Parent)
	{
		if (Parent->Handle())
		{
			int Index = (int)Parent->Items.IndexOf(this);
			RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION);
		}
		Parent->Items.Delete(this);
	}
	DeleteObj(Child);
	DeleteObj(d);
}

// 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)
{
	if (Separator())
	{
		Size.x = 8;
		Size.y = 8;
	}
	else
	{
		LFont *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();
		Size.x = BaseMenu ? 0 : 20;

		for (auto s: d->Strs)
		{
			Size.x += s->X();
		}

		if (d->Shortcut ||
			strchr(Name(), '\t'))
		{
			Size.x += 32;
		}

		if (!BaseMenu)
		{
			// leave room for child pointer
			Size.x += Child ? 8 : 0;
		}

		Size.y = BaseMenu ? GetSystemMetrics(SM_CYMENU)+1 : Ht+2;
		if (Size.y < 16) Size.y = 16;
	}
}

void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width)
{
	bool Underline = d->StartUnderline;
	LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
	for (int i=0; i < (d->Strs.Length() - (d->HasAccel ? 1 : 0)); i++)
	{
		LDisplayString *s = d->Strs[i];
		if (!s) break;

		s->Draw(pDC, x, y);
		if (Underline)
		{
			int UnderX = s->X();
			pDC->Colour(Font->Fore());
			pDC->Line(x, y+s->Y()-1, x+max(s->X()-2, 1), y+s->Y()-1);
		}

		Underline = !Underline;
		x += s->X();
	}

	if (d->HasAccel)
	{
		LDisplayString *s = *d->Strs.rbegin();
		if (s)
		{
			s->Draw(pDC, Width - s->X() - 8, y);
		}
	}
}

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);
	LRect r(0, 0, pDC->X()-1, pDC->Y()-1);
	auto Text = Name();

	if (Separator())
	{
		// paint a separator
		int Cy = pDC->Y() / 2;

		pDC->Colour(L_MENU_BACKGROUND);
		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(Selected ? L_FOCUS_SEL_FORE : L_MENU_TEXT);
		LColour Back(BaseMenu ? Menu->d->RootMenuBack : (Selected ? LColour(L_FOCUS_SEL_BACK) : LColour(L_MENU_BACKGROUND)));
		int x = IconX;
		LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
		int y = (pDC->Y() - Font->GetHeight()) >> 1;

		// paint the background
		if (BaseMenu)
		{
			// for a menu, sunken on selected
			LRect rgn = r;
			if (Selected)
			{
				LThinBorder(pDC, rgn, DefaultSunkenEdge);
				Fore = LColour(L_MENU_TEXT);
				x++;
				y++;
			}
			
			// always dialog colour
			pDC->Colour(Back);
			pDC->Rectangle(&rgn);
		}
		else
		{
			// for a sub menu
			pDC->Colour(Back);
			pDC->Rectangle();
		}

		// draw the text on top
		Font->Transparent(true);
		if (Disabled)
		{
			// disabled text
			if (!Selected)
			{
				Font->Colour(L_LIGHT);
				_PaintText(pDC, x+1, y, r.X());
			}
			// else selected... don't draw the highlight

			// "grayed" text...
			Font->Colour(L_LOW);
			_PaintText(pDC, x, y-1, r.X()-1);
		}
		else
		{
			// normal coloured text
			Font->Colour(Fore, Back);
			_PaintText(pDC, x, y-1, r.X());
		}

		LImageList *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!
			ImgLst->Draw(pDC, 0, 0, _Icon, Back);
		}
	}
}

bool LMenuItem::ScanForAccel()
{
	LString Accel;

	if (d->Shortcut)
	{
		Accel = d->Shortcut;
	}
	else
	{
		auto n = LBase::Name();
		if (n)
		{
			auto Tab = strchr(n, '\t');
			if (Tab)
				Accel = Tab + 1;
		}
	}

	if (Accel)
	{
		auto Keys = Accel.SplitDelimit("-+");
		if (Keys.Length() > 0)
		{
			int Flags = 0;
			uchar Key = 0;
			bool AccelDirty = false;
			
			for (int i=0; i<Keys.Length(); i++)
			{
				auto &k = Keys[i];

				if (k.Equals("CtrlCmd"))
				{
					k = LUiEvent::CtrlCmdName();
					AccelDirty = true;
				}
				else if (k.Equals("AltCmd"))
				{
					k = LUiEvent::AltCmdName();
					AccelDirty = true;
				}

				if (k.Equals("Ctrl") || k.Equals("Control"))
				{
					Flags |= LGI_EF_CTRL;
				}
				else if (k.Equals("Alt") || k.Equals("Option"))
				{
					Flags |= LGI_EF_ALT;
				}
				else if (k.Equals("Shift"))
				{
					Flags |= LGI_EF_SHIFT;
				}
				else if (stricmp(k, "Del") == 0 ||
						 stricmp(k, "Delete") == 0)
				{
					Key = VK_DELETE;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Ins") == 0 ||
						 stricmp(k, "Insert") == 0)
				{
					Key = VK_INSERT;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Home") == 0)
				{
					Key = VK_HOME;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "End") == 0)
				{
					Key = VK_END;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "PageUp") == 0 ||
						 stricmp(k, "Page Up") == 0 ||
						 stricmp(k, "Page-Up") == 0)
				{
					Key = VK_PRIOR;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "PageDown") == 0 ||
						 stricmp(k, "Page Down") == 0 ||
						 stricmp(k, "Page-Down") == 0)
				{
					Key = VK_NEXT;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Backspace") == 0)
				{
					Key = VK_BACK;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Up") == 0)
				{
					Key = VK_UP;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Down") == 0)
				{
					Key = VK_DOWN;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Left") == 0)
				{
					Key = VK_LEFT;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Right") == 0)
				{
					Key = VK_RIGHT;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Esc") == 0)
				{
					Key = VK_ESCAPE;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (stricmp(k, "Space") == 0)
				{
					Key = ' ';
				}
				else if (k[0] == 'F' && IsDigit(k[1]))
				{
					Key = VK_F1 + (int)k.LStrip("F").Int() - 1;
					Flags |= LGI_EF_IS_NOT_CHAR;
				}
				else if (IsAlpha(k[0]))
				{
					Key = toupper(k[0]);
				}
				else if (IsDigit(k[0]))
				{
					Key = k[0];
				}
				else if (strchr(",./\\[]`;\'", k[0]))
				{
					if (Flags & LGI_EF_CTRL)
					{
						switch (k[0])
						{
							case ';': Key = 186; break;
							case '=': Key = 187; break;
							case ',': Key = 188; break;
							case '_': Key = 189; break;
							case '.': Key = 190; break;
							case '/': Key = 191; break;
							case '`': Key = 192; break;
							case '[': Key = 219; break;
							case '\\': Key = 220; break;
							case ']': Key = 221; break;
							case '\'': Key = 222; break;
							default: LAssert(!"Unknown key."); break;
						}
					}
					else
					{
						Key = k[0];
					}
				}
				else
				{
					LgiTrace("%s:%i - Unknown Accel Part '%s'\n", _FL, k.Get());
				}
			}

			if (AccelDirty)
			{
				d->Shortcut = LString("+").Join(Keys);
				LString n = Name();
				LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
				d->UpdateStrings(Font, n);
			}
			
			if (Key && Menu)
			{
				Menu->Accel.Insert( new LAccelerator(Flags, Key, Id()) );
			}
		}
	}

	return false;
}

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

bool LMenuItem::Remove()
{
	bool Status = false;
	if (Parent)
	{
		int Index = (int) Parent->Items.IndexOf(this);
		Parent->Items.Delete(this);
		Status = RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION) != 0;

		int n=0;
		for (auto i: Parent->Items)
		{
			i->Position = n++;
		}
	}
	
	return Status;
}

bool LMenuItem::Update()
{
	bool Status = FALSE;

	if (Parent && Parent->Handle())
	{
		Status = SetMenuItemInfo(Parent->Handle(), Position, true, &Info) != 0;
		LAssert(Status);
	}

	return Status;
}

void LMenuItem::Icon(int i)
{
	_Icon = i;
}

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

void LMenuItem::Id(int i)
{
	Info.wID = i;
	Info.fMask |= MIIM_ID;
	Update();
}

void LMenuItem::Separator(bool s)
{
	if (s)
	{
		Info.fType |= MFT_SEPARATOR;
	}
	else
	{
		Info.fType &= ~MFT_SEPARATOR;
	}

	Info.fMask |= MIIM_TYPE;
	Update();
}

void LMenuItem::Checked(bool c)
{
	if (c)
	{
		Info.fState |= MFS_CHECKED;
	}
	else
	{
		Info.fState &= ~MFS_CHECKED;
	}
	
	Info.fMask |= MIIM_STATE;
	Update();
}

bool LMenuItem::Name(const char *Txt)
{
	bool Status = LBase::Name(Txt);
	if (Status)
	{
		LString n = NewStr(Txt);
		if (n)
		{
			// Set OS menu structure
			Info.dwTypeData = (LPWSTR)LBase::NameW();
			Info.cch = (UINT) StrlenW(LBase::NameW());
			Info.fType |= MFT_STRING;
			Info.fMask |= MIIM_TYPE | MIIM_DATA;

			LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont;
			d->UpdateStrings(Font, n);

			// Tell the OS
			Update();
		}
	}
	return Status;
}

void LMenuItem::Enabled(bool e)
{
	Info.fState &= ~(MFS_ENABLED|MFS_DISABLED|MFS_GRAYED);

	if (!e)
	{
		Info.fState |= MFS_DISABLED|MFS_GRAYED;
	}

	Info.fMask |= MIIM_STATE;
	Update();

	// LgiTrace("%s:%i - LMenuItem::Enabled(%i) %s\n", _FL, e, Name());
}

void LMenuItem::Focus(bool f)
{
	if (f)
	{
		Info.fState |= MFS_HILITE;
	}
	else
	{
		Info.fState &= ~MFS_HILITE;
	}

	Info.fMask |= MIIM_STATE;
	Update();
}

void LMenuItem::Sub(LSubMenu *s)
{
	Child = s;
	if (Child)
	{
		Info.hSubMenu = Child->Handle();
		s->Menu = Menu;
		s->Parent = this;
	}
	else
	{
		Info.hSubMenu = 0;
	}
	Info.fMask |= MIIM_SUBMENU;
	Update();
}

bool LMenuItem::Insert(int Pos)
{
	bool Status = false;

	if (Parent && Parent->Handle())
	{
		LAssert(Position >= 0);
		Position = Pos;
		Status = InsertMenuItem(Parent->Handle(),
								Position,
								true,
								&Info) != 0;
		LAssert(Status);
	}

	return Status;
}

void LMenuItem::Visible(bool i)
{
}

int LMenuItem::Id() { return Info.wID; }
const char *LMenuItem::Name() { return LBase::Name(); }
bool LMenuItem::Separator() { return (Info.fType & MFT_SEPARATOR) != 0; }
bool LMenuItem::Checked() { return (Info.fState & MF_CHECKED) != 0; }
bool LMenuItem::Enabled()
{
	return (Info.fState & MFS_DISABLED) == 0;
}
bool LMenuItem::Visible() { return true; }
bool LMenuItem::Focus() { return (Info.fState & MFS_HILITE) != 0; }
LSubMenu *LMenuItem::Sub() { return Child; }

///////////////////////////////////////////////////////////////////////////////////////////////
LMenu::LMenu(const char *AppName) : LSubMenu("", false)
{
	d = new LMenuPrivate;
	Menu = this;
	Window = NULL;
}

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

void LMenu::OnChange()
{
	if (Info && Window)
	{
		if (::GetMenu(Window->Handle()) != Info)
		{
			SetMenu(Window->Handle(), Info);
		}
	}
}

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();
		}
	}

	return MenuFont.f ? MenuFont.f : LSysFont;
}

bool LMenu::Attach(LViewI *p)
{
	Window = p;

	return true;
}

bool LMenu::Detach()
{
	bool Status = FALSE;

	if (Window)
	{
		HMENU hWndMenu = ::GetMenu(Window->Handle());
		if (hWndMenu == Info)
		{
			Status = SetMenu(Window->Handle(), NULL) != 0;
			if (Status)
			{
				Window = NULL;
			}
		}
	}

	return Status;
}

bool LMenu::OnKey(LView *v, LKey &k)
{
	if (k.Down() && k.vkey != 17)
	{
		for (auto a: Accel)
		{
			if (a->Match(k))
			{
				LMenuItem *i = FindItem(a->GetId());
				if (!i || i->Enabled())
				{
					int Cmd = a->GetId();
					Window->OnCommand(Cmd, 0, 0);
					return true;
				}
			}
		}
	}

	return false;
}

int LMenu::_OnEvent(LMessage *Msg)
{
	switch (Msg->Msg())
	{
		case WM_MENUCHAR:
		{
			short Key = Msg->A() & 0xffff;
			HMENU View = (HMENU)Msg->B();
			MENUITEMINFO Info;

			ZeroObj(Info);
			Info.cbSize = sizeof(Info);
			Info.fMask = MIIM_DATA;
			if (GetMenuItemInfo(View, 0, true, &Info))
			{
				LMenuItem *Item = (LMenuItem*)Info.dwItemData;
				if (Item)
				{
					LSubMenu *Menu = Item->Parent;
					if (Menu)
					{
						int Index=0;
						for (auto i: Menu->Items)
						{
							auto n = i->Name();
							if (n)
							{
								char *Amp = strchr(n, '&');
								if (Amp &&
									toupper(Amp[1]) == toupper(Key))
								{
									return (MNC_EXECUTE << 16) | Index;
								}
							}
							Index++;
						}
					}
				}
			}			
			break;
		}
		case WM_MEASUREITEM:
		{
			LPMEASUREITEMSTRUCT Item = (LPMEASUREITEMSTRUCT)Msg->b;
			if (Item)
			{
				LPoint Size;

				((LMenuItem*)Item->itemData)->_Measure(Size);
				Item->itemWidth = Size.x;
				Item->itemHeight = Size.y;
				return true;
			}
			break;
		}
		case WM_DRAWITEM:
		{
			LPDRAWITEMSTRUCT Item = (LPDRAWITEMSTRUCT)Msg->b;
			if (Item &&
				Item->CtlType == ODT_MENU)
			{
				LRect r = Item->rcItem;

				LScreenDC Dc(Item->hDC, Item->hwndItem);
				
				// Get the original origin. We need to do this because when
				// "animated menus" are on the offset starts at -3,-3 and throws
				// the menu items off. This is a bug in windows, but watcha
				// gonna do?
				int x, y;
				Dc.GetOrigin(x, y);
				
				// Clip and offset so that the menu item draws in client co-ords.
				Dc.SetOrigin(-r.x1+x, -r.y1+y);
				Dc.SetSize(r.X(), r.Y());

				// Paint the item...
				((LMenuItem*)Item->itemData)->_Paint(&Dc, Item->itemState);

				// Set the origin back the original value.
				Dc.SetOrigin(x, y);
				return true;
			}
			break;
		}
	}

	return 0;
}

bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId)
{
	return false;
}

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

bool LAccelerator::Match(LKey &k)
{
	if (!k.IsChar &&
		tolower(k.vkey) == tolower(Key))
	{
		if
		(
			(Ctrl() ^ k.Ctrl()) == 0 &&
			(Alt() ^ k.Alt()) == 0 &&
			(Shift() ^ k.Shift()) == 0 &&
			(!TestFlag(Flags, LGI_EF_IS_CHAR) || k.IsChar) &&
			(!TestFlag(Flags, LGI_EF_IS_NOT_CHAR) || !k.IsChar)
		)
		{
			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;
}
