/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code 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 3 of the License, or
(at your option) any later version.

Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "tools/edit_gui_common.h"


#include "../../sys/win32/win_local.h"
#include "PropertyGrid.h"

class rvPropertyGridItem
{
public:

	rvPropertyGridItem ( )
	{
	}

	idStr						mName;
	idStr						mValue;
	rvPropertyGrid::EItemType	mType;
};

/*
================
rvPropertyGrid::rvPropertyGrid

constructor
================
*/
rvPropertyGrid::rvPropertyGrid ( void )
{
	mWindow			= NULL;
	mEdit			= NULL;
	mListWndProc	= NULL;
	mSplitter		= 100;
	mSelectedItem	= -1;
	mEditItem		= -1;
	mState			= STATE_NORMAL;
}

/*
================
rvPropertyGrid::Create

Create a new property grid control with the given id and parent
================
*/
bool rvPropertyGrid::Create ( HWND parent, int id, int style )
{
	mStyle = style;

	// Create the List view
	const HMENU hmenuID = (HMENU)(intptr_t)id; // DG: apparently an int ID (instead of a handle/pointer) can be valid here, depending on window style
	mWindow = CreateWindowEx ( 0, "LISTBOX", "", WS_VSCROLL|WS_CHILD|WS_VISIBLE|LBS_OWNERDRAWFIXED|LBS_NOINTEGRALHEIGHT|LBS_NOTIFY, 0, 0, 0, 0, parent, hmenuID, win32.hInstance, 0 );
	mListWndProc = (WNDPROC)GetWindowLongPtr ( mWindow, GWLP_WNDPROC );
	SetWindowLongPtr ( mWindow, GWLP_USERDATA, (LONG_PTR)this );
	SetWindowLongPtr ( mWindow, GWLP_WNDPROC, (LONG_PTR)WndProc );

	LoadLibrary ( "Riched20.dll" );
	mEdit = CreateWindowEx ( 0, "RichEdit20A", "", WS_CHILD, 0, 0, 0, 0, mWindow, (HMENU) 999, win32.hInstance, NULL );
	SendMessage ( mEdit, EM_SETEVENTMASK, 0, ENM_KEYEVENTS );

	// Set the font of the list box
	HDC			dc;
	LOGFONT		lf;

	dc = GetDC ( mWindow );
	ZeroMemory ( &lf, sizeof(lf) );
	lf.lfHeight = -MulDiv(8, GetDeviceCaps(dc, LOGPIXELSY), 72);
	strcpy ( lf.lfFaceName, "MS Shell Dlg" );
	SendMessage ( mWindow, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	SendMessage ( mEdit, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
	ReleaseDC ( mWindow, dc );

	RemoveAllItems ( );

	return true;
}

/*
================
rvPropertyGrid::Move

Move the window
================
*/
void rvPropertyGrid::Move ( int x, int y, int w, int h, BOOL redraw )
{
	MoveWindow ( mWindow, x, y, w, h, redraw );
}

/*
================
rvPropertyGrid::StartEdit

Start editing
================
*/
void rvPropertyGrid::StartEdit ( int item, bool label )
{
	rvPropertyGridItem* gitem;
	RECT				rItem;

	gitem = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, item, 0 );
	if ( NULL == gitem )
	{
		return;
	}

	SendMessage ( mWindow, LB_GETITEMRECT, item, (LPARAM)&rItem );
	if ( label )
	{
		rItem.right = rItem.left + mSplitter - 1;
	}
	else
	{
		rItem.left = rItem.left + mSplitter + 1;
	}

	mState = STATE_EDIT;
	mEditItem = item;
	mEditLabel = label;

	SetWindowText ( mEdit, label?gitem->mName:gitem->mValue );
	MoveWindow ( mEdit, rItem.left, rItem.top + 2,
				rItem.right - rItem.left,
				rItem.bottom - rItem.top - 2, TRUE );
	ShowWindow ( mEdit, SW_SHOW );

	SetFocus ( mEdit );
}

/*
================
rvPropertyGrid::FinishEdit

Finish editing by copying the data in the edit control to the internal value
================
*/
void rvPropertyGrid::FinishEdit ( void )
{
	char				value[1024];
	rvPropertyGridItem* item;
	bool				update;

	if ( mState != STATE_EDIT )
	{
		return;
	}

	assert ( mEditItem >= 0 );

	mState = STATE_FINISHEDIT;

	update = false;
	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, mEditItem, 0 );
	assert ( item );

	GetWindowText ( mEdit, value, 1023 );

	if ( !value[0] )
	{
		mState = STATE_EDIT;
		MessageBeep ( MB_ICONASTERISK );
		return;
	}

	if ( !mEditLabel && item->mValue.Cmp ( value ) )
	{
		NMPROPGRID nmpg;
		nmpg.hdr.code = PGN_ITEMCHANGED;
		nmpg.hdr.hwndFrom = mWindow;
		nmpg.hdr.idFrom = GetWindowLong ( mWindow, GWL_ID );
		nmpg.mName  = item->mName;
		nmpg.mValue = value;

		if ( !SendMessage ( GetParent ( mWindow ), WM_NOTIFY, 0, (LPARAM)&nmpg ) )
		{
			mState = STATE_EDIT;
			SetFocus ( mEdit );
			return;
		}

		// The item may have been destroyed and recreated in the notify call so get it again
		item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, mEditItem, 0 );
		if ( item )
		{
			item->mValue = value;
			update = true;
		}
	}
	else if ( mEditLabel && item->mName.Cmp ( value ) )
	{
		int sel;
		sel = AddItem ( value, "", PGIT_STRING );
		SetCurSel ( sel );
		StartEdit ( sel, false );
		return;
	}

	SetCurSel ( mEditItem );

	mState = STATE_NORMAL;
	mEditItem = -1;

	ShowWindow ( mEdit, SW_HIDE );
	SetFocus ( mWindow );
}

/*
================
rvPropertyGrid::CancelEdit

Stop editing without saving the data
================
*/
void rvPropertyGrid::CancelEdit ( void )
{
	if ( mState == STATE_EDIT && !mEditLabel )
	{
		if ( !*GetItemValue ( mEditItem ) )
		{
			RemoveItem ( mEditItem );
		}
	}

	mSelectedItem = mEditItem;
	mEditItem = -1;
	mState = STATE_NORMAL;
	ShowWindow ( mEdit, SW_HIDE );
	SetFocus ( mWindow );
	SetCurSel ( mSelectedItem );
}

/*
================
rvPropertyGrid::AddItem

Add a new item to the property grid
================
*/
int rvPropertyGrid::AddItem ( const char* name, const char* value, EItemType type )
{
	rvPropertyGridItem* item;
	int					insert;

	// Cant add headers if headers arent enabled
	if ( type == PGIT_HEADER && !(mStyle&PGS_HEADERS) )
	{
		return -1;
	}

	item = new rvPropertyGridItem;
	item->mName = name;
	item->mValue = value;
	item->mType = type;

	insert = SendMessage(mWindow,LB_GETCOUNT,0,0) - ((mStyle&PGS_ALLOWINSERT)?1:0);

	return SendMessage ( mWindow, LB_INSERTSTRING, insert, (LPARAM)item );
}

/*
================
rvPropertyGrid::RemoveItem

Remove the item at the given index
================
*/
void rvPropertyGrid::RemoveItem ( int index )
{
	if ( index < 0 || index >= SendMessage ( mWindow, LB_GETCOUNT, 0, 0 ) )
	{
		return;
	}

	delete (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );

	SendMessage ( mWindow, LB_DELETESTRING, index, 0 );
}

/*
================
rvPropertyGrid::RemoveAllItems

Remove all items from the property grid
================
*/
void rvPropertyGrid::RemoveAllItems ( void )
{
	int i;

	// free the memory for all the items
	for ( i = SendMessage ( mWindow, LB_GETCOUNT, 0, 0 ); i > 0; i -- )
	{
		delete (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, i - 1, 0 );
	}

	// remove all items from the listbox itself
	SendMessage ( mWindow, LB_RESETCONTENT, 0, 0 );

	if ( mStyle & PGS_ALLOWINSERT )
	{
		// Add the item used to add items
		rvPropertyGridItem* item;
		item = new rvPropertyGridItem;
		item->mName = "";
		item->mValue = "";
		SendMessage ( mWindow, LB_ADDSTRING, 0, (LPARAM)item );
	}
}

/*
================
rvPropertyGrid::GetItemName

Return name of item at given index
================
*/
const char* rvPropertyGrid::GetItemName ( int index )
{
	rvPropertyGridItem* item;

	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );
	if ( !item )
	{
		return "";
	}

	return item->mName;
}

/*
================
rvPropertyGrid::GetItemValue

Return value of item at given index
================
*/
const char* rvPropertyGrid::GetItemValue ( int index )
{
	rvPropertyGridItem* item;

	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );
	if ( !item )
	{
		return "";
	}

	return item->mValue;
}

/*
================
rvPropertyGrid::WndProc

Window procedure for property grid
================
*/
LRESULT CALLBACK rvPropertyGrid::WndProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	rvPropertyGrid* grid = (rvPropertyGrid*) GetWindowLongPtr ( hWnd, GWLP_USERDATA );

	switch ( msg )
	{
		case WM_SETFOCUS:
//			grid->mEditItem = -1;
			break;

		case WM_KEYDOWN:
		{
			NMKEY nmkey;
			nmkey.hdr.code = NM_KEYDOWN;
			nmkey.hdr.hwndFrom = grid->mWindow;
			nmkey.nVKey = wParam;
			nmkey.uFlags = HIWORD(lParam);
			nmkey.hdr.idFrom = GetWindowLongPtr ( hWnd, GWL_ID );
			SendMessage ( GetParent ( hWnd ), WM_NOTIFY, nmkey.hdr.idFrom, (LPARAM)&nmkey );
			break;
		}

		case WM_CHAR:
		{
			switch ( wParam )
			{
				case VK_RETURN:
					if ( grid->mSelectedItem >= 0 )
					{
						grid->StartEdit ( grid->mSelectedItem, (*grid->GetItemName ( grid->mSelectedItem ))?false:true);
					}
					break;
			}
			break;
		}

		case WM_KILLFOCUS:
			grid->mSelectedItem = -1;
			break;

		case WM_NOTIFY:
		{
			NMHDR* hdr;
			hdr = (NMHDR*)lParam;
			if ( hdr->idFrom == 999 )
			{
				if ( hdr->code == EN_MSGFILTER )
				{
					MSGFILTER* filter;
					filter = (MSGFILTER*)lParam;
					if ( filter->msg == WM_KEYDOWN )
					{
						switch ( filter->wParam )
						{
							case VK_RETURN:
							case VK_TAB:
								grid->FinishEdit ( );
								return 1;

							case VK_ESCAPE:
								grid->CancelEdit ( );
								return 1;
						}
					}

					if ( filter->msg == WM_CHAR || filter->msg == WM_KEYUP )
					{
						switch ( filter->wParam )
						{
							case VK_RETURN:
							case VK_TAB:
							case VK_ESCAPE:
								return 1;
						}
					}
				}
			}
			break;
		}

		case WM_COMMAND:
			if ( lParam == (LPARAM)grid->mEdit )
			{
				if ( HIWORD(wParam) == EN_KILLFOCUS )
				{
					grid->FinishEdit ( );
					return true;
				}
			}
			break;

		case WM_LBUTTONDBLCLK:
			grid->mSelectedItem = SendMessage ( hWnd, LB_ITEMFROMPOINT, 0, lParam );

			// fall through

		case WM_LBUTTONDOWN:
		{
			int					item;
			rvPropertyGridItem* gitem;
			RECT				rItem;
			POINT				pt;

			if ( grid->mState == rvPropertyGrid::STATE_EDIT )
			{
				break;
			}

			item  = (short)LOWORD(SendMessage ( hWnd, LB_ITEMFROMPOINT, 0, lParam ));
			if ( item == -1 )
			{
				break;
			}

			gitem = (rvPropertyGridItem*)SendMessage ( hWnd, LB_GETITEMDATA, item, 0 );
			pt.x  = LOWORD(lParam);
			pt.y  = HIWORD(lParam);

			SendMessage ( hWnd, LB_GETITEMRECT, item, (LPARAM)&rItem );

			if ( !gitem->mName.Icmp ( "" ) )
			{
				rItem.right = rItem.left + grid->mSplitter - 1;
				if ( PtInRect ( &rItem, pt) )
				{
					grid->SetCurSel ( item );
					grid->StartEdit ( item, true );
				}
			}
			else if ( grid->mSelectedItem == item )
			{
				rItem.left = rItem.left + grid->mSplitter + 1;
				if ( PtInRect ( &rItem, pt) )
				{
					grid->StartEdit ( item, false );
				}
			}

			if ( grid->mState == rvPropertyGrid::STATE_EDIT )
			{
				ClientToScreen ( hWnd, &pt );
				ScreenToClient ( grid->mEdit, &pt );
				SendMessage ( grid->mEdit, WM_LBUTTONDOWN, wParam, MAKELONG(pt.x,pt.y) );
				return 0;
			}

			break;
		}

		case WM_ERASEBKGND:
		{
			RECT rClient;
			GetClientRect ( hWnd, &rClient );
			FillRect ( (HDC)wParam, &rClient, GetSysColorBrush ( COLOR_3DFACE ) );
			return TRUE;
		}

		case WM_SETCURSOR:
		{
			POINT point;
			GetCursorPos ( &point );
			ScreenToClient ( hWnd, &point );
			if ( point.x >= grid->mSplitter - 2 && point.x <= grid->mSplitter + 2 )
			{
				SetCursor ( LoadCursor ( NULL, IDC_SIZEWE));
				return TRUE;
			}
			break;
		}
	}

	return CallWindowProc ( grid->mListWndProc, hWnd, msg, wParam, lParam );
}

/*
================
rvPropertyGrid::ReflectMessage

Handle messages sent to the parent window
================
*/
bool rvPropertyGrid::ReflectMessage ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch ( msg )
	{
		case WM_COMMAND:
		{
			if ( (HWND)lParam == mWindow )
			{
				switch ( HIWORD(wParam) )
				{
					case LBN_SELCHANGE:
						mSelectedItem = SendMessage ( mWindow, LB_GETCURSEL, 0, 0 );
						break;
				}
			}
			break;
		}

		case WM_DRAWITEM:
			HandleDrawItem ( wParam, lParam );
			return true;

		case WM_MEASUREITEM:
		{
			float scaling_factor = Win_GetWindowScalingFactor(hWnd);

			MEASUREITEMSTRUCT* mis = (MEASUREITEMSTRUCT*) lParam;
			mis->itemHeight = 18 * scaling_factor;
			return true;
		}
	}

	return false;
}

/*
================
rvPropertyGrid::HandleDrawItem

Handle the draw item message
================
*/
int rvPropertyGrid::HandleDrawItem ( WPARAM wParam, LPARAM lParam )
{
	DRAWITEMSTRUCT*		dis  = (DRAWITEMSTRUCT*) lParam;
	rvPropertyGridItem* item = (rvPropertyGridItem*) dis->itemData;
	RECT				rTemp;
	HBRUSH				brush;

	if ( !item )
	{
		return 0;
	}

	rTemp = dis->rcItem;
	if ( mStyle & PGS_HEADERS )
	{
		brush = GetSysColorBrush ( COLOR_SCROLLBAR );
		rTemp.right = rTemp.left + 10;
		FillRect ( dis->hDC, &rTemp, brush );
		rTemp.left = rTemp.right;
		rTemp.right = dis->rcItem.right;
	}

	if ( item->mType == PGIT_HEADER )
	{
		brush = GetSysColorBrush ( COLOR_SCROLLBAR );
	}
	else if ( dis->itemState & ODS_SELECTED )
	{
		brush = GetSysColorBrush ( COLOR_HIGHLIGHT );
	}
	else
	{
		brush = GetSysColorBrush ( COLOR_WINDOW );
	}

	FillRect ( dis->hDC, &rTemp, brush );

	HPEN pen = CreatePen ( PS_SOLID, 1, GetSysColor ( COLOR_SCROLLBAR ) );
	HPEN oldpen = (HPEN)SelectObject ( dis->hDC, pen );
	MoveToEx ( dis->hDC, dis->rcItem.left, dis->rcItem.top, NULL );
	LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.top );
	MoveToEx ( dis->hDC, dis->rcItem.left, dis->rcItem.bottom, NULL );
	LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.bottom);

	if ( item->mType != PGIT_HEADER )
	{
		MoveToEx ( dis->hDC, dis->rcItem.left + mSplitter, dis->rcItem.top, NULL );
		LineTo ( dis->hDC, dis->rcItem.left + mSplitter, dis->rcItem.bottom );
	}
	SelectObject ( dis->hDC, oldpen );
	DeleteObject ( pen );

	int colorIndex = ( (dis->itemState & ODS_SELECTED ) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT );
	SetTextColor ( dis->hDC, GetSysColor ( colorIndex ) );
	SetBkMode ( dis->hDC, TRANSPARENT );
	SetBkColor ( dis->hDC, GetSysColor ( COLOR_3DFACE ) );

	RECT rText;
	rText = rTemp;
	rText.right = rText.left + mSplitter;
	rText.left += 2;

	DrawText ( dis->hDC, item->mName, item->mName.Length(), &rText, DT_LEFT|DT_VCENTER|DT_SINGLELINE );

	rText.left = dis->rcItem.left + mSplitter + 2;
	rText.right = dis->rcItem.right;
	DrawText ( dis->hDC, item->mValue, item->mValue.Length(), &rText, DT_LEFT|DT_VCENTER|DT_SINGLELINE );

	return 0;
}