/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- April 2000
 */

// CustomizeMenus.cpp : implementation file
//

#include "stdafx.h"
#include "wincvs.h"
#include "CustomizeMenus.h"
#include "MacrosSetup.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define INVTAB(msg, e) {msg, #e, e}
static CustInvertTable sInitInvTable[] =
{
	INVTAB("Separator", ID_SEPARATOR),
	INVTAB("Pr&eferences...\tCtrl+F1", ID_APP_PREFERENCES),
	INVTAB("&Command Line...", ID_APP_CMDLINE),
	INVTAB("&Login...", ID_APP_LOGIN),
	INVTAB("Lo&gout", ID_APP_LOGOUT),
	INVTAB("&Stop cvs", ID_STOPCVS),
	INVTAB("&Create a new repository...", ID_VIEW_INIT),
	INVTAB("&Import module from selection...", ID_VIEW_IMPORT),
	INVTAB("Chec&kout module to selection...", ID_VIEW_CHECKOUT),
	INVTAB("Create a &tag by module...", ID_APP_RTAGCREATE),
	INVTAB("&Delete a tag by module...", ID_APP_RTAGDELETE),
	INVTAB("Create a &branch by module...", ID_APP_RTAGBRANCH),
	INVTAB("&Update selection...\tCtrl+U", ID_VIEW_UPDATE),
	INVTAB("Co&mmit selection...\tCtrl+M", ID_VIEW_COMMIT),
	INVTAB("&Add selection", ID_VIEW_ADD),
	INVTAB("Add selection &binary", ID_VIEW_ADDB),
	INVTAB("&Remove selection", ID_VIEW_RMV),
	INVTAB("Erase &selection\tCtrl+Backspace", ID_VIEW_TRASH),
	INVTAB("Create a &tag on selection...", ID_VIEW_TAGNEW),
	INVTAB("&Delete a tag on selection...", ID_VIEW_TAGDELETE),
	INVTAB("Create a &branch on selection...", ID_VIEW_TAGBRANCH),
	INVTAB("E&xplore selection\tF2", ID_VIEW_EXPLORE),
	INVTAB("&Query Update\tF4", ID_VIEW_QUERYUPDATE),
	INVTAB("R&eload view\tF5", ID_VIEW_RELOAD),
	INVTAB("View &selection\tF7", ID_VIEW_EDITSEL),
	INVTAB("View &default", ID_VIEW_EDITSELDEF),
	INVTAB("&Diff selection\tAlt+=", ID_VIEW_DIFF),
	INVTAB("&Log selection...", ID_VIEW_LOG),
	INVTAB("&Status selection", ID_VIEW_STATUS),
	INVTAB("&Graph selection...\tCtrl+G", ID_VIEW_GRAPH),
	INVTAB("&Annotate selection...", ID_QUERY_ANNOTATE),
	INVTAB("&Lock selection", ID_VIEW_LOCKF),
	INVTAB("&Unlock selection", ID_VIEW_UNLOCKF),
	INVTAB("&Edit selection", ID_VIEW_EDIT),
	INVTAB("&Unedit selection", ID_VIEW_UNEDIT),
	INVTAB("&Add a Watch on selection", ID_VIEW_WATCHON),
	INVTAB("&Remove a watch from selection", ID_VIEW_WATCHOFF),
	INVTAB("Release &selection", ID_VIEW_RELEASE),
	INVTAB("&Watchers of selection", ID_VIEW_WATCHERS),
	INVTAB("E&ditors of selection", ID_VIEW_EDITORS),
	{0L, 0L, 0}
};

class CCustInvertTable : public std::vector<CustInvertTable>
{
public:
	CCustInvertTable();
	virtual ~CCustInvertTable();

	// get the macro inside the persistent cmd table
	void Initialize();

protected:
	std::vector<char *> m_garbage;
} sInvTable;

CCustInvertTable::CCustInvertTable()
{
	// init the cmd table with the static MFC commands
	const CustInvertTable *tmp = sInitInvTable;
	CustInvertTable newEntry;
	while(tmp->key != 0L)
	{
		newEntry = *tmp;
		push_back(newEntry);
		tmp++;
	}
}

CCustInvertTable::~CCustInvertTable()
{
	std::vector<char *>::iterator i;
	for(i = m_garbage.begin(); i != m_garbage.end(); ++i)
	{
		free(*i);
	}
}

void CCustInvertTable::Initialize()
{
	if(m_garbage.size() != 0)
		return;

	vector<CMacroEntry>::const_iterator i;
	int cnt;
	CustInvertTable newEntry;
	for(cnt = 0, i = gMacrosAdmin.entries.begin(); i != gMacrosAdmin.entries.end(); ++i, cnt++)
	{
		newEntry.cmd = ID_MACRO_ADMIN + cnt;
		char *name = strdup((*i).name);
		m_garbage.push_back(name);
		newEntry.key = name;
		newEntry.title = name;
		push_back(newEntry);
	}
	for(cnt = 0, i = gMacrosSel.entries.begin(); i != gMacrosSel.entries.end(); ++i, cnt++)
	{
		newEntry.cmd = ID_MACRO_SEL + cnt;
		char *name = strdup((*i).name);
		m_garbage.push_back(name);
		newEntry.key = name;
		newEntry.title = name;
		push_back(newEntry);
	}
}

const CustInvertTable *CustGetByKey(const char *entry)
{
	std::vector<CustInvertTable>::const_iterator tmp;
	sInvTable.Initialize();
	for(tmp = sInvTable.begin(); tmp != sInvTable.end(); ++tmp)
	{
		if(strcmp(entry, (*tmp).key) == 0)
			return &*tmp;
	}
	return 0L;
}

static char *GetInvString(const CustInvertTable *entry)
{
	static CString res;
	res = entry->title;
	int ind;
	if((ind = res.Find('&')) != -1)
		res.Delete(ind);
	if((ind = res.Find('\t')) != -1)
		res = res.Left(ind);
	return (char *)(const char *)res;
}

static bool IsInInvertList(const CustInvertTable *entry, const std::vector<UStr> & list)
{
	std::vector<UStr>::const_iterator i;
	for(i = list.begin(); i != list.end(); ++i)
	{
		const CustInvertTable *tab = CustGetByKey(*i);
		if(tab == 0L)
			continue;

		if(strcmp(entry->key, tab->key) == 0)
			return true;
	}
	return false;
}

/////////////////////////////////////////////////////////////////////////////
// CCustomizeMenus dialog


CCustomizeMenus::CCustomizeMenus(CMMenuString *fromMenu, CWnd* pParent /*=NULL*/)
	: CDialog(CCustomizeMenus::IDD, pParent), m_Menu(fromMenu)
{
	//{{AFX_DATA_INIT(CCustomizeMenus)
	//}}AFX_DATA_INIT
}


void CCustomizeMenus::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CCustomizeMenus)
	DDX_Control(pDX, IDC_MENULIST, m_menuList);
	DDX_Control(pDX, IDC_ALLCMDS, m_cmdsList);
	DDX_Control(pDX, IDC_UP, m_upbtn);
	DDX_Control(pDX, IDC_REMOVE, m_removebtn);
	DDX_Control(pDX, IDC_DOWN, m_downbtn);
	DDX_Control(pDX, IDC_ADD, m_addbtn);
	//}}AFX_DATA_MAP

	if(!pDX->m_bSaveAndValidate)
	{
		// initialize the buttons
		m_upbtn.EnableWindow(FALSE);
		m_downbtn.EnableWindow(FALSE);
		m_addbtn.EnableWindow(FALSE);
		m_removebtn.EnableWindow(FALSE);
		m_cmdsList.SetExtendedStyle(
			m_cmdsList.GetExtendedStyle() |
			LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);
		m_menuList.SetExtendedStyle(
			m_menuList.GetExtendedStyle() |
			LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
	}
}


BEGIN_MESSAGE_MAP(CCustomizeMenus, CDialog)
	//{{AFX_MSG_MAP(CCustomizeMenus)
	ON_BN_CLICKED(IDC_REMOVE, OnRemove)
	ON_BN_CLICKED(IDC_ADD, OnAdd)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_ALLCMDS, OnItemchangedAllcmds)
	ON_NOTIFY(NM_DBLCLK, IDC_MENULIST, OnDblclkMenulist)
	ON_NOTIFY(NM_DBLCLK, IDC_ALLCMDS, OnDblclkAllcmds)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_MENULIST, OnItemchangedMenulist)
	ON_BN_CLICKED(IDC_DOWN, OnDown)
	ON_BN_CLICKED(IDC_UP, OnUp)
	ON_NOTIFY(LVN_BEGINDRAG, IDC_MENULIST, OnBegindragMenulist)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCustomizeMenus message handlers

void CCustomizeMenus::OnRemove() 
{
	int nItem = m_menuList.GetNextItem(-1, LVNI_SELECTED);
	if(nItem == -1)
		return;

	const CustInvertTable *data = (const CustInvertTable *)
		m_menuList.GetItemData(nItem);

	if(data->cmd != ID_SEPARATOR)
	{
		LV_ITEM lvi;
		lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
		lvi.iItem = m_cmdsList.GetItemCount();
		lvi.iSubItem = 0;
		lvi.lParam = (DWORD)data;
		lvi.pszText = GetInvString(data);
		lvi.iImage = GetIcon(data);

		int newItem = m_cmdsList.InsertItem(&lvi);
		VERIFY(m_cmdsList.EnsureVisible(newItem, TRUE));
		VERIFY(m_cmdsList.SetItemState(newItem, LVIS_SELECTED, LVIS_SELECTED));
	}

	VERIFY(m_menuList.DeleteItem(nItem));
}

void CCustomizeMenus::OnAdd() 
{
	int nItem = m_cmdsList.GetNextItem(-1, LVNI_SELECTED);
	if(nItem == -1)
		return;

	const CustInvertTable *data = (const CustInvertTable *)
		m_cmdsList.GetItemData(nItem);

	LV_ITEM lvi;
	lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
	lvi.iItem = m_menuList.GetItemCount();
	lvi.iSubItem = 0;
	lvi.lParam = (DWORD)data;
	lvi.pszText = GetInvString(data);
	lvi.iImage = GetIcon(data);

	int newItem = m_menuList.InsertItem(&lvi);
	VERIFY(m_menuList.EnsureVisible(newItem, TRUE));
	VERIFY(m_menuList.SetItemState(newItem, LVIS_SELECTED, LVIS_SELECTED));

	if(data->cmd != ID_SEPARATOR)
	{
		VERIFY(m_cmdsList.DeleteItem(nItem));
	}
}

static int CALLBACK _Compare(LPARAM data1, LPARAM data2, LPARAM data)
{
	CustInvertTable *d1 = (CustInvertTable *)data1;
	CustInvertTable *d2 = (CustInvertTable *)data2;

	CString s1(GetInvString(d1));
	CString s2(GetInvString(d2));
	int cmp = strcmp(s1, s2);
	if(cmp == 0)
		return 0;
	if(d1->cmd == ID_SEPARATOR)
		return -1;
	if(d2->cmd == ID_SEPARATOR)
		return 1;
	return cmp;
}

void CCustomizeMenus::OnOK()
{
	std::vector<UStr> & list = m_Menu->GetList();
	list.erase(list.begin(), list.end());

	int nItem = -1;
	while((nItem = m_menuList.GetNextItem(nItem, LVNI_ALL)) != -1)
	{
		const CustInvertTable *data = (const CustInvertTable *)
			m_menuList.GetItemData(nItem);
		
		list.push_back(data->key);
	}
	
	CDialog::OnOK();
}

BOOL CCustomizeMenus::OnInitDialog() 
{
	m_SmallImageList.Create(IDR_CUSTMENUS, 16, 1, RGB(255, 255, 255));
	sInvTable.Initialize();
	
	CDialog::OnInitDialog();
	
	LV_COLUMN lvc;
	int cnt;
	const std::vector<UStr> & list = m_Menu->GetList();
	
	// Get the client rect of the list view control
	CRect rect;
	m_cmdsList.GetClientRect(&rect);

	// Make the width of the column to the width of the client area minus
	// the width of a scroll bar
	int nWidth = rect.right - ::GetSystemMetrics(SM_CXVSCROLL);

	// first the left list
	lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
	lvc.iSubItem = 0;
	lvc.cx = nWidth;
	lvc.fmt = LVCFMT_LEFT;
	m_cmdsList.InsertColumn(0, &lvc);
	m_cmdsList.SetImageList(&m_SmallImageList, LVSIL_SMALL);

	std::vector<CustInvertTable>::const_iterator table;
	for(cnt = 0, table = sInvTable.begin(); table != sInvTable.end(); ++table, cnt++)
	{
		const CustInvertTable & tablEntry = *table;
		if(table->cmd != ID_SEPARATOR && IsInInvertList(&tablEntry, list))
			continue;

		LV_ITEM lvi;
		lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
		lvi.iItem = cnt;
		lvi.iSubItem = 0;
		lvi.lParam = (DWORD)&tablEntry;
		lvi.pszText = GetInvString(&tablEntry);
		lvi.iImage = GetIcon(table);

		m_cmdsList.InsertItem(&lvi);
	}

	m_cmdsList.SortItems(_Compare, 0L);

	// then the right one
	lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM;
	lvc.iSubItem = 0;
	lvc.cx = nWidth;
	lvc.fmt = LVCFMT_LEFT;
	m_menuList.InsertColumn(0, &lvc);
	m_menuList.SetImageList(&m_SmallImageList, LVSIL_SMALL);

	std::vector<UStr>::const_iterator i;

	for(cnt = 0, i = list.begin(); i != list.end(); ++i, cnt++)
	{
		const CustInvertTable *tab = CustGetByKey(*i);
		if(tab == 0L)
			continue;

		LV_ITEM lvi;
		lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
		lvi.iItem = cnt;
		lvi.iSubItem = 0;
		lvi.lParam = (DWORD)tab;
		lvi.pszText = GetInvString(tab);
		lvi.iImage = GetIcon(tab);

		m_menuList.InsertItem(&lvi);
	}

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CCustomizeMenus::OnItemchangedAllcmds(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	
	*pResult = 0;
	int nItem = m_cmdsList.GetNextItem(-1, LVNI_SELECTED);
	m_addbtn.EnableWindow(nItem != -1);
}

void CCustomizeMenus::OnDblclkMenulist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	OnRemove();
	
	*pResult = 0;
}

void CCustomizeMenus::OnDblclkAllcmds(NMHDR* pNMHDR, LRESULT* pResult) 
{
	OnAdd();
	
	*pResult = 0;
}

void CCustomizeMenus::OnItemchangedMenulist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	
	*pResult = 0;
	int nItem = m_menuList.GetNextItem(-1, LVNI_SELECTED);
	m_removebtn.EnableWindow(nItem != -1);

	if(nItem == -1)
	{
		m_upbtn.EnableWindow(FALSE);
		m_downbtn.EnableWindow(FALSE);
	}
	else if(nItem == 0)
	{
		m_upbtn.EnableWindow(FALSE);
		m_downbtn.EnableWindow(m_menuList.GetItemCount() > 1 ? TRUE : FALSE);
	}
	else if(nItem == m_menuList.GetItemCount() - 1)
	{
		m_upbtn.EnableWindow(TRUE);
		m_downbtn.EnableWindow(FALSE);
	}
	else
	{
		m_upbtn.EnableWindow(TRUE);
		m_downbtn.EnableWindow(TRUE);
	}
}

void CCustomizeMenus::OnMove(int offset)
{
	int nItem = m_menuList.GetNextItem(-1, LVNI_SELECTED);
	if(nItem == -1)
		return;

	const CustInvertTable *data = (const CustInvertTable *)
		m_menuList.GetItemData(nItem);

	int newPos = nItem + offset;
	if(newPos < 0 || newPos > m_menuList.GetItemCount())
		return;

	VERIFY(m_menuList.DeleteItem(nItem));

	LV_ITEM lvi;
	lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
	lvi.iItem = newPos;
	lvi.iSubItem = 0;
	lvi.lParam = (DWORD)data;
	lvi.pszText = GetInvString(data);
	lvi.iImage = GetIcon(data);

	int newItem = m_menuList.InsertItem(&lvi);
	VERIFY(m_menuList.EnsureVisible(newItem, TRUE));
	VERIFY(m_menuList.SetItemState(newItem, LVIS_SELECTED, LVIS_SELECTED));
}

void CCustomizeMenus::OnDown() 
{
	OnMove(1);
}

void CCustomizeMenus::OnUp() 
{
	OnMove(-1);
}

void CCustomizeMenus::OnBegindragMenulist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	// TODO: Add your control notification handler code here
	
	*pResult = 0;
}

int CCustomizeMenus::GetIcon(const CustInvertTable *entry)
{
	if(entry->cmd == ID_SEPARATOR)
		return 0;

	if(entry->cmd >= ID_MACRO_SEL && entry->cmd < (ID_MACRO_SEL + 100))
		return 2;

	if(entry->cmd >= ID_MACRO_ADMIN && entry->cmd < (ID_MACRO_ADMIN + 100))
		return 2;

	return 1;
}
