/*hdr
**      FILE:           LMenuGtk.cpp
**      AUTHOR:         Matthew Allen
**      DATE:           19/4/2013
**      DESCRIPTION:    Gtk menus
**
**      Copyright (C) 2013, Matthew Allen
**              fret@memecode.com
*/
#include <math.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"

using namespace Gtk;
#define DEBUG_MENUS		0

#if DEBUG_MENUS
	#define LOG(...)	printf(__VA_ARGS__)
#else
	#define LOG(...)
#endif

///////////////////////////////////////////////////////////////////////////////////////////////
static ::LArray<LSubMenu*> Active;

void SubMenuDestroy(LSubMenu *Item)
{
	// LgiTrace("DestroySub %p %p\n", Item, Item->Info);
	Item->Info = NULL;
}

LSubMenu::LSubMenu(const char *name, bool Popup)
{
	Menu = NULL;
	Parent = NULL;
	Info = NULL;
	_ContextMenuId = NULL;
	ActiveTs = 0;
	InLoop = false;
	
	if (name)
	{
		Info = GtkCast(Gtk::gtk_menu_new(), gtk_menu_shell, GtkMenuShell);
		// LgiTrace("CreateSub %p %p\n", this, Info);
		Gtk::g_signal_connect_data(	Info,
									"destroy",
									(Gtk::GCallback) SubMenuDestroy,
									this,
									NULL,
									Gtk::G_CONNECT_SWAPPED);
	}

	Active.Add(this);
}

LSubMenu::~LSubMenu()
{
	Active.Delete(this);

	while (Items.Length())
	{
		LMenuItem *i = Items[0];
		LAssert(i->Parent == this);
		DeleteObj(i);
	}
	
	Info.Destroy([](GtkMenuShell *s){ gtk_widget_destroy(GTK_WIDGET(s)); });
}

// This will be run in the GUI thread..
gboolean LSubMenuClick(LMouse *m)
{
	if (!m)
		return false;

	bool OverMenu = false, HasVisible = false;
	uint64 ActiveTs = 0;

	for (auto s: Active)
	{
		auto w = GTK_WIDGET(s->Handle());
		auto vis = gtk_widget_is_visible(w);
		if (vis)
		{
			// auto src = gtk_widget_get_screen(w);
			// auto hnd = gdk_screen_get_root_window(src);
			auto hnd = gtk_widget_get_window(w);

			ActiveTs = MAX(s->ActiveTs, ActiveTs);
			HasVisible = true;

			GdkRectangle a;
			gdk_window_get_frame_extents(hnd, &a);
			/*
			LgiTrace("SubClk down=%i, pos=%i,%i sub=%i,%i-%i,%i\n",
				m->Down(),
				m->x, m->y,
				a.x, a.y, a.width, a.height);
				*/

			LRect rc = a;
			if (rc.Overlap(m->x, m->y))
				OverMenu = true;
		}
	}

	if (m->Down() && !OverMenu && HasVisible && ActiveTs > 0)
	{
		uint64 Now = LCurrentTime();
		uint64 Since = Now - ActiveTs;
		LgiTrace("%s:%i - LSubMenuClick, Since=" LPrintfInt64 "\n", _FL, Since);

		if (Since > 500)
		{
			for (auto s: Active)
			{
				auto w = GTK_WIDGET(s->Handle());
				auto vis = gtk_widget_is_visible(w);
				if (vis && s->ActiveTs)
				{
					gtk_widget_hide(w);
					s->ActiveTs = 0;
				}
			}
		}
	}
	else LOG("LSubMenuClick Down=%i OverMenu=%i HasVIsible=%i ActiveTs=%i\n", m->Down(), OverMenu, HasVisible, ActiveTs > 0);

	DeleteObj(m);

	return false;
}

// This is not called in the GUI thread..
void LSubMenu::SysMouseClick(LMouse &m)
{
	LMouse *ms = new LMouse;
	*ms = m;
	g_idle_add((GSourceFunc) LSubMenuClick, ms);
}

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 < 0 ? Items.Length() : Where, Shortcut);
	if (i)
	{
		i->Enabled(Enabled);

		Items.Insert(i, Where);

		GtkWidget *item = GTK_WIDGET(i->Handle());
		LAssert(item);
		if (item)
		{
			gtk_menu_shell_append(Info, item);
			gtk_widget_show(item);
		}

		return i;
	}

	return 0;
}

LMenuItem *LSubMenu::AppendSeparator(int Where)
{
	LMenuItem *i = new LMenuItem;
	if (i)
	{
		i->Parent = this;
		i->Menu = Menu;
		i->Id(-2);

		Items.Insert(i, Where);

		GtkWidget *item = GTK_WIDGET(i->Handle());
		LAssert(item);
		if (item)
		{
			gtk_menu_shell_append(Info, item);
			gtk_widget_show(item);
		}
		
		return i;
	}

	return 0;
}

LSubMenu *LSubMenu::AppendSub(const char *Str, int Where)
{
	LBase::Name(Str);
	LMenuItem *i = new LMenuItem(Menu, this, Str, Where < 0 ? Items.Length() : Where, NULL);
	if (i)
	{
		i->Id(-1);
		Items.Insert(i, Where);

		GtkWidget *item = GTK_WIDGET(i->Handle());
		LAssert(item);
		if (item)
		{
			if (Where < 0)
				gtk_menu_shell_append(Info, item);
			else
				gtk_menu_shell_insert(Info, item, Where);
			gtk_widget_show(item);
		}

		i->Child = new LSubMenu(Str);
		if (i->Child)
		{
			i->Child->Parent = i;
			i->Child->Menu = Menu;
			i->Child->Window = Window;
			
			GtkWidget *sub = GTK_WIDGET(i->Child->Handle());
			LAssert(sub);
			if (i->Handle() && sub)
			{
				gtk_menu_item_set_submenu(i->Handle(), sub);
				gtk_widget_show(sub);
			}
			else
				LgiTrace("%s:%i Error: gtk_menu_item_set_submenu(%p,%p) failed\n", _FL, i->Handle(), sub);
		}
				
		return i->Child;
	}

	return 0;
}

void LSubMenu::ClearHandle()
{
	Info = NULL;
	for (auto i: Items)
	{
		i->ClearHandle();
	}
}

void LSubMenu::Empty()
{
	LMenuItem *i;
	while ((i = Items[0]))
	{
		RemoveItem(i);
		DeleteObj(i);
	}
}

bool LSubMenu::RemoveItem(int i)
{
	return RemoveItem(Items.ItemAt(i));
}

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

	return false;
}

bool LSubMenu::IsContext(LMenuItem *Item)
{
	if (!_ContextMenuId)
	{
		LMenuItem *i = GetParent();
		LSubMenu *s = i ? i->GetParent() : NULL;
		if (s)
			// Walk up the chain of menus to find the top...
			return s->IsContext(Item);
		else
			// Ok we got to the top
			return false;
	}
	
	*_ContextMenuId = Item->Id();
	Gtk::gtk_main_quit();
	return true;
}

void GtkDeactivate(Gtk::GtkMenuShell *widget, LSubMenu *Sub)
{
	Sub->OnActivate(false);
}

void LSubMenu::OnActivate(bool a)
{
	if (!a)
	{
		if (_ContextMenuId)
			*_ContextMenuId = 0;
		
		if (InLoop)
		{
			Gtk::gtk_main_quit();
			InLoop = false;
		}
	}
}

int LSubMenu::Float(LView *From, int x, int y, int Button)
{
	#ifdef __GTK_H__
	LWindow *Wnd = From->GetWindow();
	if (!Wnd)
		return -1;
	Wnd->Capture(false);
	#else
	while (From && !From->Handle())
	{
		From = dynamic_cast<LView*>(From->GetParent());
	}		
	
	if (!From || !From->Handle())
	{
		LOG("%s:%i - No menu handle\n", _FL);
		return -1;
	}
	
	if (From->IsCapturing())
		From->Capture(false);
	#endif

	ActiveTs = LCurrentTime();

	// This signal handles the case where the user cancels the menu by clicking away from it.
	Info.Connect("deactivate",  (GCallback)GtkDeactivate, this);
	
	auto Widget = GTK_WIDGET(Info.obj);
	gtk_widget_show_all(Widget);

	int MenuId = 0;
	_ContextMenuId = &MenuId;

	LPoint Pos(x, y);
	gtk_menu_popup(GTK_MENU(Info.obj),
					NULL, NULL, NULL, NULL,
					Button,
					gtk_get_current_event_time());

	InLoop = true;
	gtk_main();
	_ContextMenuId = NULL;
	return MenuId;
}

LSubMenu *LSubMenu::FindSubMenu(int Id)
{
	for (auto i: Items)
	{
		LSubMenu *Sub = i->Sub();

		// LOG("Find(%i) '%s' %i sub=%p\n", Id, i->Name(), i->Id(), 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));
		}
	}
};

static LAutoString MenuItemParse(const char *s)
{
	char buf[256], *out = buf;
	const char *in = s;
	while (in && *in && out < buf + sizeof(buf) - 1)
	{
		if (*in == '_')
		{
			*out++ = '_';
			*out++ = '_';
		}
		else if (*in != '&' || in[1] == '&')
		{
			*out++ = *in;
		}
		else
		{
			*out++ = '_';
		}
		
		in++;
	}
	*out++ = 0;
	
	char *tab = strrchr(buf, '\t');
	if (tab)
	{
		*tab++ = 0;
	}
	
	return LAutoString(NewStr(buf));
}

static void MenuItemActivate(GtkMenuItem *MenuItem, LMenuItem *Item)
{
	Item->OnGtkEvent("activate");
}

static void MenuItemDestroy(GtkWidget *widget, LMenuItem *Item)
{
	Item->OnGtkEvent("destroy");
}

void LMenuItem::OnGtkEvent(::LString Event)
{
	if (Event.Equals("activate"))
	{
		if (!Sub() && !InSetCheck)
		{
			LSubMenu *Parent = GetParent();
		
			if (!Parent || !Parent->IsContext(this))
			{
				auto m = GetMenu();
				if (m)
				{
					// Attached to a menu, so send an event to the window
					LViewI *w = m->WindowHandle();
					if (w)
						w->PostEvent(M_COMMAND, Id());
					else
						LAssert(!"No window for menu to send to");
				}
				else
				{
					// Could be just a popup menu... in which case do nothing.				
				}
			}
		}
	}
	else if (Event.Equals("destroy"))
	{
		// LgiTrace("DestroyItem %p %p\n", this, Info);
		Info = NULL;
	}
}

LMenuItem::LMenuItem()
{
	d = new LMenuItemPrivate();
	Info = NULL;
	Child = NULL;
	Menu = NULL;
	Parent = NULL;
	InSetCheck = false;

	Handle(GTK_MENU_ITEM(gtk_separator_menu_item_new()));
	
	Position = -1;
	
	_Flags = 0;	
	_Icon = -1;
	_Id = 0;
}

LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int id, int Pos, const char *shortcut)
{
	d = NULL;
	LAutoString Txt = MenuItemParse(txt);
	LBase::Name(txt);
	Info = NULL;

	Handle(GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(Txt)));
	
	Child = NULL;
	Menu = m;
	Parent = p;
	InSetCheck = false;

	Position = Pos;

	_Flags = 0;	
	_Icon = -1;
	_Id = id;

	ShortCut = shortcut;
	ScanForAccel();
}

void LMenuItem::Handle(GtkMenuItem *mi)
{
	LAssert(Info == NULL);	
	if (Info != mi)
	{
		Info = mi;
		Gtk::g_signal_connect(Info, "activate", (Gtk::GCallback) MenuItemActivate, this);
		Gtk::g_signal_connect(Info, "destroy", (Gtk::GCallback) MenuItemDestroy, this);
	}
}

LMenuItem::~LMenuItem()
{
	Remove();
	Info.Destroy([](GtkMenuItem *i){ gtk_widget_destroy(GTK_WIDGET(i)); });
	DeleteObj(Child);
	DeleteObj(d);
}

#if GtkVer(2, 24)
#include <gdk/gdkkeysyms-compat.h>
#endif

#ifndef GDK_F1
enum {
	GDK_F1 = 0xffbe,
	GDK_F2,
	GDK_F3,
	GDK_F4,
	GDK_F5,
	GDK_F6,
	GDK_F7,
	GDK_F8,
	GDK_F9,
	GDK_F10,
	GDK_F11,
	GDK_F12
};
#endif

// /usr/include/gtk-3.0/gdk/gdkkeysyms-compat.h
Gtk::gint LgiKeyToGtkKey(int Key, const char *ShortCut)
{
	#ifdef GDK_a
	LAssert(GDK_a == 'a');
	LAssert(GDK_A == 'A');
	#endif
	
	/*
	if (Key >= 'a' && Key <= 'z')
		return Key;
	*/
	if (Key >= 'A' && Key <= 'Z')
		return Key;
	if (Key >= '0' && Key <= '9')
		return Key;
	
	switch (Key)
	{
		#ifdef GDK_Delete
		case LK_DELETE: return GDK_Delete;
		#endif
		#ifdef GDK_Insert
		case LK_INSERT: return GDK_Insert;
		#endif
		#ifdef GDK_Home
		case LK_HOME: return GDK_Home;
		#endif
		#ifdef GDK_End
		case LK_END: return GDK_End;
		#endif
		#ifdef GDK_Page_Up
		case LK_PAGEUP: return GDK_Page_Up;
		#endif
		#ifdef GDK_Page_Down
		case LK_PAGEDOWN: return GDK_Page_Down;
		#endif
		#ifdef GDK_BackSpace
		case LK_BACKSPACE: return GDK_BackSpace;
		#endif
		#ifdef GDK_Escape
		case LK_ESCAPE: return GDK_Escape;
		#else
		#warning "GDK_Escape not defined."
		#endif

		case LK_UP:
			return GDK_Up;
		case LK_DOWN:
			return GDK_Down;
		case LK_LEFT:
			return GDK_Left;
		case LK_RIGHT:
			return GDK_Right;

		case LK_F1:
			return GDK_F1;
		case LK_F2:
			return GDK_F2;
		case LK_F3:
			return GDK_F3;
		case LK_F4:
			return GDK_F4;
		case LK_F5:
			return GDK_F5;
		case LK_F6:
			return GDK_F6;
		case LK_F7:
			return GDK_F7;
		case LK_F8:
			return GDK_F8;
		case LK_F9:
			return GDK_F9;
		case LK_F10:
			return GDK_F10;
		case LK_F11:
			return GDK_F11;
		case LK_F12:
			return GDK_F12;
		case ' ':
			#ifdef GDK_space
			return GDK_space;
			#else
			return ' ';
			#endif
		case ',':
			return GDK_comma;
		default:
			LgiTrace("Unhandled menu accelerator: 0x%x (%s)\n", Key, ShortCut);
			break;
	}
	
	return 0;
}

#if 1

bool LMenuItem::ScanForAccel()
{
	if (!Menu)
		return false;

	const char *Sc = ShortCut;
	if (!Sc)
	{
		char *n = LBase::Name();
		if (n)
		{
			char *Tab = strchr(n, '\t');
			if (Tab)
				Sc = Tab + 1;
		}
	}

	if (!Sc)
		return false;

	GToken Keys(Sc, "+-");
	if (Keys.Length() > 0)
	{
		int Flags = 0;
		char16 Key = 0;
		
		for (int i=0; i<Keys.Length(); i++)
		{
			char *k = Keys[i];

			if (!stricmp(k, "CtrlCmd"))
			{
				k = LUiEvent::CtrlCmdName();
			}
			else if (!stricmp(k, "AltCmd"))
			{
				k = LUiEvent::AltCmdName();
			}			
		
			if (stricmp(k, "Ctrl") == 0)
			{
				#ifdef MAC
				Flags |= LGI_EF_SYSTEM;
				#else
				Flags |= LGI_EF_CTRL;
				#endif
			}
			else if (stricmp(k, "Alt") == 0)
			{
				#ifdef MAC
				Flags |= LGI_EF_SYSTEM;
				#else
				Flags |= LGI_EF_ALT;
				#endif
			}
			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, Sc);
			}
		}
		
		if (Key)
		{
			Gtk::gint GtkKey = LgiKeyToGtkKey(Key, Sc);
			if (GtkKey)
			{
				GtkWidget *w = GtkCast(Info.obj, gtk_widget, GtkWidget);
				Gtk::GdkModifierType mod = (Gtk::GdkModifierType)
					(
						(TestFlag(Flags, LGI_EF_CTRL)   ? Gtk::GDK_CONTROL_MASK : 0) |
						(TestFlag(Flags, LGI_EF_SHIFT)  ? Gtk::GDK_SHIFT_MASK : 0)   |
						(TestFlag(Flags, LGI_EF_ALT)    ? Gtk::GDK_MOD1_MASK : 0)    |
						(TestFlag(Flags, LGI_EF_SYSTEM) ? Gtk::GDK_META_MASK : 0)
					);

				const char *Signal = "activate";
	
				gtk_widget_add_accelerator(	w,
											Signal,
											Menu->AccelGrp,
											GtkKey,
											mod,
											Gtk::GTK_ACCEL_VISIBLE
										);
				gtk_widget_show_all(w);
			}
			else
			{
				LOG("%s:%i - No gtk key for '%s'\n", _FL, Sc);
			}
			
			auto Ident = Id();
			LAssert(Ident > 0);
			Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) );
		}
		else
		{
			LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Sc);
			return false;
		}
	}

	return true;
}

#else

bool LMenuItem::ScanForAccel()
{
	::LString Accel;

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

	if (!Accel)
		return false;

	auto Keys = Accel.SplitDelimit("-+");
	if (Keys.Length() == 0)
		return false;

	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 (k.Equals("System"))
		{
			Flags |= LGI_EF_SYSTEM;
		}
		else if (stricmp(k, "Del") == 0 ||
				 stricmp(k, "Delete") == 0)
		{
			Key = LK_DELETE;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Ins") == 0 ||
				 stricmp(k, "Insert") == 0)
		{
			Key = LK_INSERT;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Home") == 0)
		{
			Key = LK_HOME;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "End") == 0)
		{
			Key = LK_END;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "PageUp") == 0 ||
				 stricmp(k, "Page Up") == 0 ||
				 stricmp(k, "Page-Up") == 0)
		{
			Key = LK_PAGEUP;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "PageDown") == 0 ||
				 stricmp(k, "Page Down") == 0 ||
				 stricmp(k, "Page-Down") == 0)
		{
			Key = LK_PAGEDOWN;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Backspace") == 0)
		{
			Key = LK_BACKSPACE;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Up") == 0)
		{
			Key = LK_UP;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Down") == 0)
		{
			Key = LK_DOWN;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Left") == 0)
		{
			Key = LK_LEFT;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Right") == 0)
		{
			Key = LK_RIGHT;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Esc") == 0)
		{
			Key = LK_ESCAPE;
			Flags |= LGI_EF_IS_NOT_CHAR;
		}
		else if (stricmp(k, "Space") == 0)
		{
			Key = ' ';
		}
		else if (k[0] == 'F' && IsDigit(k[1]))
		{
			Key = LK_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
		{
			LAssert(!"Unknown Accel Part");
		}
	}

	if (Key)
	{
		Gtk::gint GtkKey = LgiKeyToGtkKey(Key, Accel);
		if (GtkKey)
		{
			GtkWidget *w = GtkCast(Info.obj, gtk_widget, GtkWidget);
			Gtk::GdkModifierType mod = (Gtk::GdkModifierType)
				(
					(TestFlag(Flags, LGI_EF_CTRL)   ? Gtk::GDK_CONTROL_MASK : 0) |
					(TestFlag(Flags, LGI_EF_SHIFT)  ? Gtk::GDK_SHIFT_MASK : 0)   |
					(TestFlag(Flags, LGI_EF_ALT)    ? Gtk::GDK_MOD1_MASK : 0)    |
					(TestFlag(Flags, LGI_EF_SYSTEM) ? Gtk::GDK_META_MASK : 0)
				);

			const char *Signal = "activate";

			gtk_widget_add_accelerator(	w,
										Signal,
										Menu->AccelGrp,
										GtkKey,
										mod,
										Gtk::GTK_ACCEL_VISIBLE
									);
			gtk_widget_show_all(w);
		}
		else
		{
			LOG("%s:%i - No gtk key for '%s'\n", _FL, Accel.Get());
		}
		
		auto Ident = Id();
		LAssert(Ident > 0);
		Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) );
		
		return true;
	}
	else
	{
		LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Accel.Get());
		return false;
	}
}

#endif

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

void LMenuItem::ClearHandle()
{
	Info = NULL;
	if (Child)
		Child->ClearHandle();
}

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

	if (Info)
	{
		Gtk::GtkWidget *w = GTK_WIDGET(Info.obj);
		if (Gtk::gtk_widget_get_parent(w))
		{		
			Gtk::GtkContainer *c = GtkCast(Parent->Info.obj, gtk_container, GtkContainer);
			Gtk::gtk_container_remove(c, w);
		}
	}

	LAssert(Parent->Items.HasItem(this));
	Parent->Items.Delete(this);
	Parent = NULL;
	return true;
}

void LMenuItem::Id(int i)
{
	_Id = i;
}

void LMenuItem::Separator(bool s)
{
	if (s)
	{
		_Id = -2;
	}
}

struct MenuItemIndex
{
	Gtk::GtkWidget *w;
	int Current;
	int Index;
	
	MenuItemIndex()
	{
		Index = -1;
		Current = 0;
		w = NULL;
	}
};

static void
FindMenuItemIndex(Gtk::GtkWidget *w, Gtk::gpointer data)
{
	MenuItemIndex *d = (MenuItemIndex*)data;

	LOG("w=%p d->w=%p name=%s cur=%i d->Index=%i\n", w, d->w, G_OBJECT_TYPE_NAME(w), d->Current, d->Index);
	if (w == d->w)
		d->Index = d->Current;
	d->Current++;
}

int
gtk_container_get_child_index(GtkContainer *c, GtkWidget *child)
{
	MenuItemIndex Idx;
	if (c && child)
	{
		Idx.w = child;
		gtk_container_foreach(c, FindMenuItemIndex, &Idx);
	}
	return Idx.Index;
}

bool LMenuItem::Replace(Gtk::GtkWidget *newWid)
{
	if (!newWid || !Info)
	{
		LgiTrace("%s:%i - Error: New=%p Old=%p\n", newWid, Info.obj);
		return false;
	}

	// Get widget
	GtkWidget *w = GTK_WIDGET(Info.obj);
	
	// Is is attach already?
	if (gtk_widget_get_parent(w))
	{
		// Yes!
		GtkContainer *c = GTK_CONTAINER(Parent->Info.obj);
		if (c)
		{			
			// Find index
			int PIdx = Parent->Items.IndexOf(this);
			LAssert(PIdx >= 0);
		
			// Remove old item
			gtk_container_remove(c, w);
		
			// Add new item
			gtk_menu_shell_insert(Parent->Info, newWid, PIdx);
			gtk_widget_show(newWid);
		}
		else LgiTrace("%s:%i - GTK_CONTAINER failed.\n", _FL);
	}
	else
	{
		// Delete it
		g_object_unref(Info);
	}
	
	Handle(GTK_MENU_ITEM(newWid));
	return Info != NULL;
}

LImageList *LMenuItem::GetImageList()
{
	if (GetMenu())
		return GetMenu()->GetImageList();

	// Search tree of parents for an image list...
	for (auto p = GetParent(); p; )
	{
		auto lst = p->GetImageList();
		if (lst)
			return lst;
			
		auto pmi = p->GetParent();
		if (pmi)
			p = pmi->GetParent();
		else
			break;
	}

	return NULL;
}

gboolean
LgiMenuItemDraw(GtkWidget *widget, cairo_t *cr, LMenuItem *mi)
{
	auto cls = GTK_WIDGET_GET_CLASS(widget);
	cls->draw(widget, cr);
	mi->PaintIcon(cr);
	return true;
}

void LMenuItem::PaintIcon(Gtk::cairo_t *cr)
{
	if (_Icon < 0)
		return;
	auto il = GetImageList();
	if (!il)
		return;

	auto wid = GTK_WIDGET(Info.obj);
	GtkAllocation a;
	gtk_widget_get_allocation(wid, &a);

	GdkRGBA bk = {0};
	gtk_style_context_get_background_color(	gtk_widget_get_style_context(wid),
											gtk_widget_get_state_flags(wid),
											&bk);

	LScreenDC Dc(cr, a.width, a.height);
	il->Draw(&Dc, 7, 5, _Icon, bk.alpha ? LColour(bk) : LColour::White);
}

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

	if (Info)
		Info.Connect("draw", (GCallback)LgiMenuItemDraw, this);
}

void LMenuItem::Checked(bool c)
{
	if (c)
		SetFlag(_Flags, ODS_CHECKED);
	else
		ClearFlag(_Flags, ODS_CHECKED);

	if (Info)
	{
		InSetCheck = true;
		
		// Is the item a checked menu item?
		if (!GTK_IS_CHECK_MENU_ITEM(Info.obj) && c)
		{
			// Create a checkable menu item...
			LAutoString Txt = MenuItemParse(Name());
			GtkWidget *w = gtk_check_menu_item_new_with_mnemonic(Txt);

			// Attach our signal
			gulong ret = g_signal_connect_data(	w,
												"activate",
												(GCallback) MenuItemActivate,
												this,
												NULL,
												G_CONNECT_SWAPPED);

			// Replace the existing menu item with this new one
			if (w)
				Replace(w);
			else
				LAssert(!"No new widget.");
		}
		
		if (GTK_IS_CHECK_MENU_ITEM(Info.obj))
		{
			// Now mark is checked
			GtkCheckMenuItem *chk = GTK_CHECK_MENU_ITEM(Info.obj);
			if (chk)
			{
				Gtk::gtk_check_menu_item_set_active(chk, c);
			}
		}
		
		InSetCheck = false;
	}
}

bool LMenuItem::Name(const char *n)
{
	bool Status = LBase::Name(n);	
	
	#if GtkVer(2, 16)
	LAssert(Info);
	LAutoString Txt = MenuItemParse(n);
	ScanForAccel();
	gtk_menu_item_set_label(Info, Txt);
	#else
	LgiTrace("Warning: can't set label after creation.");
	Status = false;
	#endif
	
	return Status;
}

void LMenuItem::Enabled(bool e)
{
	if (e)
		ClearFlag(_Flags, ODS_DISABLED);
	else
		SetFlag(_Flags, ODS_DISABLED);
	
	if (Info)
	{
		gtk_widget_set_sensitive(GtkCast(Info.obj, gtk_widget, GtkWidget), e);
 	}
}

void LMenuItem::Focus(bool f)
{
}

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

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()
{
	return !TestFlag(_Flags, ODS_DISABLED);
}

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

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

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

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

///////////////////////////////////////////////////////////////////////////////////////////////
struct LMenuFont
{
	LFont *f;

	LMenuFont()
	{
		f = NULL;
	}

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

class LMenuPrivate
{
public:
};

LMenu::LMenu(const char *AppName) : LSubMenu("", false)
{
	Menu = this;
	d = NULL;
	AccelGrp = Gtk::gtk_accel_group_new();
	Info = GtkCast(Gtk::gtk_menu_bar_new(), gtk_menu_shell, GtkMenuShell);
}

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

LFont *LMenu::GetFont()
{
	if (!MenuFont.f)
	{
		LFontType Type;
		if (Type.GetSystemFont("Menu"))
		{
			MenuFont.f = Type.Create();
			if (MenuFont.f)
			{
				// MenuFont.f->CodePage(LSysFont->CodePage());
			}
			else
			{
				LOG("LMenu::GetFont Couldn't create menu font.\n");
			}
		}
		else
		{
			LOG("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)
{
	if (!p)
	{
		LAssert(0);
		return false;
	}
		
	LWindow *Wnd = p->GetWindow();
	if (!Wnd)
	{
		LAssert(0);
		return false;
	}
		
	Window = Wnd;
	if (Wnd->_VBox)
	{
		LAssert(!"Already has a menu");
		return false;
	}

	
	Gtk::GtkWidget *menubar = GTK_WIDGET(Info.obj);

	Wnd->_VBox = Gtk::gtk_box_new(Gtk::GTK_ORIENTATION_VERTICAL, 0);

	Gtk::GtkBox *vbox = GTK_BOX(Wnd->_VBox);
	Gtk::GtkContainer *wndcontainer = GTK_CONTAINER(Wnd->Wnd);

	g_object_ref(Wnd->_Root);

	gtk_container_remove(wndcontainer, Wnd->_Root);
	gtk_box_pack_start(vbox, menubar, false, false, 0);
	gtk_box_pack_end(vbox, Wnd->_Root, true, true, 0);
	gtk_container_add(wndcontainer, Wnd->_VBox);

	gtk_widget_show_all(GTK_WIDGET(Wnd->Wnd));

	g_object_unref(Wnd->_Root);

	gtk_window_add_accel_group(Wnd->Wnd, AccelGrp);
	
	#if 0
	gtk_widget_queue_resize(GTK_WIDGET(vbox));
	#else
	GdkRectangle allocation = Wnd->GetClient();
	g_signal_emit_by_name(G_OBJECT(vbox), "size-allocate", GTK_WIDGET(vbox), &allocation, NULL, NULL);
	#endif
	
	return true;
}

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

bool LMenu::SetPrefAndAboutItems(int a, int b)
{
	return false;
}

bool LMenu::OnKey(LView *v, LKey &k)
{
	if (k.Down())
	{
		for (auto a: Accel)
		{
			if (a->Match(k))
			{
				LAssert(a->GetId() > 0);
				Window->OnCommand(a->GetId(), 0, 0);
				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
					{
						char *n = s->Name();
						if (ValidStr(n))
						{
							char *Amp = strchr(n, '&');
							if (Amp)
							{
								while (Amp && Amp[1] == '&')
									Amp = strchr(Amp + 2, '&');

								char16 Accel = tolower(Amp[1]);
								char16 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.vkey;
	
	if (k.vkey == LK_RSHIFT ||
		k.vkey == LK_LSHIFT ||
		k.vkey == LK_RCTRL ||
		k.vkey == LK_LCTRL ||
		k.vkey == LK_RALT ||
		k.vkey == LK_RALT)
	{
		return false;
	}
	
	#if 1
	LOG("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s%s\n",
		Press,
		Press>=' '?Press:'.',
		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":"",
		TestFlag(Flags, LGI_EF_SYSTEM)?" system":""
		);
	#endif

	if (toupper(Press) == (uint)Key)
	{
		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) &&
			((TestFlag(Flags, LGI_EF_SYSTEM) ^ k.System()) == 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;
}
