/*hdr
**      FILE:           GWidgets.cpp
**      AUTHOR:         Matthew Allen
**      DATE:           8/9/1998
**      DESCRIPTION:    Dialog components
**
**      Copyright (C) 1998-2001 Matthew Allen
**              fret@memecode.com
*/

#include <stdlib.h>
#include <stdio.h>
#include "lgi/common/Lgi.h"
#include <commctrl.h>
#include "lgi/common/TableLayout.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/Button.h"
#include "lgi/common/LgiRes.h"

#define USE_DIALOGBOXINDIRECTPARAM         0

struct LDialogPriv
{
	bool IsModal, IsModeless, _Resizable;
	int ModalStatus;
	int BtnId;

	#if WINNATIVE
    #if USE_DIALOGBOXINDIRECTPARAM
	GMem *Mem;
    #else
    int ModalResult;
	#endif
	#elif defined BEOS

	sem_id ModalSem;
	int ModalRet;

	#endif

    LDialogPriv()
    {
        #if USE_DIALOGBOXINDIRECTPARAM
    	Mem = 0;
    	#else
    	ModalResult = -1;
    	#endif
    	IsModal = false;
		IsModeless = false;
		BtnId = -1;
    }
    
    ~LDialogPriv()
    {
    	#if USE_DIALOGBOXINDIRECTPARAM
        DeleteObj(Mem);
        #endif
    }
};

///////////////////////////////////////////////////////////////////////////////////////////
#if USE_DIALOGBOXINDIRECTPARAM
short *DlgStrCopy(short *A, char *N)
{
	uchar *n = (uchar*) N;
	if (n)
	{
		while (*n)
		{
			*A++ = *n++;
		}
	}
	*A++ = 0;
	return A;
}

short *DlgStrCopy(short *A, char16 *n)
{
	if (n)
	{
		while (*n)
		{
			*A++ = *n++;
		}
	}
	*A++ = 0;
	return A;
}

short *DlgPadToDWord(short *A, bool Seek = false)
{
	char *c = (char*) A;
	while (((NativeInt) c) & 3)
	{
		if (Seek)
		{
			c++;
		}
		else
		{
			*c++ = 0;
		}
	}
	return (short*) c;
}
#endif

///////////////////////////////////////////////////////////////////////////////////////////
LDialog::LDialog()
	: ResObject(Res_Dialog)
{
	d = new LDialogPriv;
	_Window = this;
	Name("Dialog");

	#if USE_DIALOGBOXINDIRECTPARAM
	WndFlags |= GWF_DIALOG;
	SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT);
	#else
	SetStyle(GetStyle() & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX));
	SetStyle(GetStyle() | WS_DLGFRAME);
	#endif
}

LDialog::~LDialog()
{
    DeleteObj(d);
}

bool LDialog::LoadFromResource(int Resource, char *TagList)
{
	LAutoString n;
	bool Status = LResourceLoad::LoadFromResource(Resource, this, &Pos, &n, TagList);
	if (Status && n)
		Name(n);
	return Status;
}

LRESULT CALLBACK DlgRedir(HWND hWnd, UINT m, WPARAM a, LPARAM b)
{
	if (m == WM_INITDIALOG)
	{
		LDialog *NewWnd = (LDialog*) b;
		NewWnd->_View = hWnd;
		#if _MSC_VER >= _MSC_VER_VS2005
        SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)(LViewI*)NewWnd);
		#else
        SetWindowLong(hWnd, GWL_USERDATA, (LONG)(LViewI*)NewWnd);
		#endif
	}

	LViewI *Wnd = (LViewI*)
		#if _MSC_VER >= _MSC_VER_VS2005
		#pragma warning(disable : 4312)
		GetWindowLongPtr(hWnd, GWLP_USERDATA);
		#pragma warning(default : 4312)
		#else
		GetWindowLong(hWnd, GWL_USERDATA);
		#endif
	if (Wnd)
	{
		LMessage Msg(m, a, b);
		return Wnd->OnEvent(&Msg);
	}

	return 0;
}

bool LDialog::OnRequestClose(bool OsClose)
{
	return true;
}

bool LDialog::IsModal()
{
	return d->IsModal;
}

int LDialog::DoModal(OsView ParentHnd)
{
	int Status = -1;

	d->IsModal = true;
	
	#if USE_DIALOGBOXINDIRECTPARAM
	
	d->Mem = new GMem(16<<10);
	if (d->Mem)
	{
		DLGTEMPLATE *Template = (DLGTEMPLATE*) d->Mem->Lock();
		if (Template)
		{
			LRect r = GetPos();

			r.x1 = (double)r.x1 / DIALOG_X;
			r.y1 = (double)r.y1 / DIALOG_Y;
			r.x2 = (double)r.x2 / DIALOG_X;
			r.y2 = (double)r.y2 / DIALOG_Y;

			Template->style =	// DS_ABSALIGN |
								DS_SETFONT |
								DS_NOFAILCREATE |
								DS_3DLOOK |
								WS_SYSMENU |
								WS_CAPTION |
								DS_SETFOREGROUND |
								DS_MODALFRAME;

			Template->dwExtendedStyle = WS_EX_DLGMODALFRAME;
			Template->cdit = 0;
			Template->x = r.x1;
			Template->y = r.y1;
			Template->cx = r.X();
			Template->cy = r.Y();

			short *A = (short*) (Template+1);
			// menu
			*A++ = 0;
			// class
			*A++ = 0;
			// title
			A = DlgStrCopy(A, NameW());
			// font
			*A++ = LSysFont->PointSize();
			A = DlgStrCopy(A, LSysFont->Face());
			A = DlgPadToDWord(A);

			LViewI *p = GetParent();
			while (	p &&
					!p->Handle() &&
					p->GetParent())
			{
				p = p->GetParent();
			}

			HWND hWindow = 0;
			if (ParentHnd)
			{
				hWindow = ParentHnd;
			}
			else if (p)
			{
				hWindow = p->Handle();
			}

			Status = DialogBoxIndirectParam(LProcessInst(),
											Template,
											hWindow,
											(DLGPROC) DlgRedir, 
											(LPARAM) this);
		}

		DeleteObj(d->Mem);
	}
	
	#else

    LViewI *p = GetParent();
    if (p && p->GetWindow() != p)
        p = p->GetWindow();
    
	if (Attach(0))
	{
	    AttachChildren();
	    
	    LWindow *ParentWnd = dynamic_cast<LWindow*>(p);
	    if (ParentWnd)
	        ParentWnd->_Dialog = this;

	    if (p)
	    {
	        LRect pp = p->GetPos();
			if (pp.Valid())
			{
				int cx = pp.x1 + (pp.X() >> 1);
				int cy = pp.y1 + (pp.Y() >> 1);
	        
				LRect np = GetPos();
				np.Offset(  cx - (np.X() >> 1) - np.x1,
							cy - (np.Y() >> 1) - np.y1);
	        
				SetPos(np);
				MoveOnScreen();
			}
	    }
	    
	    Visible(true);
	    
	    OsView pwnd = ParentHnd ? ParentHnd : (p ? p->Handle() : NULL);
	    if (pwnd) EnableWindow(pwnd, false);
	    while (d->ModalResult < 0)
	    {
	        LYield();
	        LSleep(20);
	    }
	    if (pwnd) EnableWindow(pwnd, true);
	    if (ParentWnd)
	        ParentWnd->_Dialog = NULL;
	    
	    Visible(false);
	    Status = d->ModalResult;
	}
	else
	{
	    LAssert(!"Attach failed.");
	}
	
	#endif

	return Status;
}

static char *BaseStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

int LDialog::DoModeless()
{
	int Status = -1;

	LAssert(!_View);
	if (_View)
		return Status;

	d->IsModeless = true;
	d->IsModal = false;

	#if USE_DIALOGBOXINDIRECTPARAM
	d->Mem = new GMem(16<<10);
	LRect OldPos = GetPos();
	if (d->Mem)
	{
		LONG DlgUnits = GetDialogBaseUnits();
		uint16 old_baseunitX = DlgUnits & 0xffff;
		uint16 old_baseunitY = DlgUnits >> 16;
		LDisplayString Bs(LSysFont, BaseStr);
		int baseunitX = (Bs.X() / 26 + 1) / 2;
		int baseunitY = Bs.Y();
		
		DLGTEMPLATE *Template = (DLGTEMPLATE*) d->Mem->Lock();
		if (Template)
		{
			LRect r = Pos;
			r.x1 = MulDiv(r.x1, 4, baseunitX);
			r.y1 = MulDiv(r.y1, 8, baseunitY);
			r.x2 = MulDiv(r.x2, 4, baseunitX);
			r.y2 = MulDiv(r.y2, 8, baseunitY);

			Template->style =	WS_VISIBLE |
								DS_ABSALIGN |
								WS_SYSMENU |
								WS_CAPTION |
								DS_SETFONT |
								DS_NOFAILCREATE |
								DS_3DLOOK;

			Template->style |= DS_MODALFRAME;
			Template->dwExtendedStyle = WS_EX_DLGMODALFRAME;
			Template->cdit = 0;
			Template->x = r.x1;
			Template->y = r.y1;
			Template->cx = r.X();
			Template->cy = r.Y();

			short *A = (short*) (Template+1);
			// menu
			*A++ = 0;
			// class
			*A++ = 0;
			// title
			A = DlgStrCopy(A, NameW());
			// font
			*A++ = LSysFont->PointSize(); // point size
			A = DlgStrCopy(A, LSysFont->Face());
			A = DlgPadToDWord(A);

			LViewI *p = GetParent();
			while (	p &&
					!p->Handle() &&
					p->GetParent())
			{
				p = p->GetParent();
			}

			HWND hWindow = (p)?p->Handle():0;

			CreateDialogIndirectParam(	LProcessInst(),
										Template,
										hWindow,
										(DLGPROC) DlgRedir, 
										(LPARAM) this);
		}
	}

	#else

    LViewI *p = GetParent();
    if (p && p->GetWindow() != p)
        p = p->GetWindow();
    
	if (Attach(0))
	{
	    AttachChildren();
	    
		if (p && Handle() && p->Handle())
		{
			#ifdef _WIN64
			SetWindowLongPtr(Handle(), GWLP_HWNDPARENT, (LONG_PTR)p->Handle());
			#else
			SetWindowLong(Handle(), GWL_HWNDPARENT, (LONG)p->Handle());
			#endif
		}

	    if (p)
	    {
	        LRect pp = p->GetPos();
	        int cx = pp.x1 + (pp.X() >> 1);
	        int cy = pp.y1 + (pp.Y() >> 1);
	        
	        LRect np = GetPos();
	        np.Offset(  cx - (np.X() >> 1) - np.x1,
	                    cy - (np.Y() >> 1) - np.y1);
	        
	        SetPos(np);
	        MoveOnScreen();
	    }
	    
	    Visible(true);
        Status = true;
	}
	else
	{
	    LAssert(!"Attach failed.");
	}

	#endif

	return Status;
}

LMessage::Result LDialog::OnEvent(LMessage *Msg)
{
	switch (Msg->m)
	{
		#if USE_DIALOGBOXINDIRECTPARAM
		case WM_INITDIALOG:
		#else
		case WM_CREATE:
		#endif
		{
			LRect r = Pos;
			Pos.ZOff(-1, -1);
			SetPos(r);	// resets the dialog to the correct
						// size when large fonts are used

			if (GetAlwaysOnTop())
				SetAlwaysOnTop(true);

			AttachChildren();
			if (!_Default)
				SetDefault(FindControl(IDOK));

			LResources::StyleElement(this);			

			// This was commented out. I've re-introduced it until such time
			// as there is a good reason not to have it enabled. If such a reason
			// arises, update this comment to reflect that.
			OnCreate();

    		#if USE_DIALOGBOXINDIRECTPARAM
			LViewI *v = LAppInst->GetFocus();
			LWindow *w = v ? v->GetWindow() : NULL;
			if (v && (w != v) && (w == this))
			{
				// Application has set the focus, don't set to default focus.
				return false;
			}
			else
			{
				// Set the default focus.
				return true;
			}
			#endif
			
			// If we don't return true here the LWindow::OnEvent handler for
			// WM_CREATE will call OnCreate again.
			return true;
		}
	}

    return LWindow::OnEvent(Msg);
}

int LDialog::GetButtonId()
{
	return d->BtnId;
}

int LDialog::OnNotify(LViewI *Ctrl, LNotification n)
{
	LButton *b = dynamic_cast<LButton*>(Ctrl);
	if (b)
	{
		d->BtnId = b->GetId();
		
		if (d->IsModal)
			EndModal(d->BtnId);
		else if (d->IsModeless)
			EndModeless();
	}

	return 0;
}

void LDialog::Quit(bool DontDelete)
{
	if (d->IsModal)
		EndModal(0);
	else
		LView::Quit(DontDelete);
}

void LDialog::OnPosChange()
{
    if (Children.Length() == 1)
    {
        List<LViewI>::I it = Children.begin();
        LLayout *t = dynamic_cast<LLayout*>((LViewI*)it);
        if (t)
        {
            LRect r = GetClient();
            r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing);
            t->SetPos(r);
        }
    }
}

void LDialog::EndModal(int Code)
{
	if (d->IsModal)
	{
		#if USE_DIALOGBOXINDIRECTPARAM
		EndDialog(Handle(), Code);
		#else
		d->ModalResult = max(Code, 0);
		#endif
	}
}

void LDialog::EndModeless(int Code)
{
	if (d->IsModeless)
		Quit(Code != 0);
}

///////////////////////////////////////////////////////////////////////////////////////////
LControl::LControl(char *SubClassName) : LView(0)
{
	SubClass = 0;
	SetOnDelete = NULL;
	if (SubClassName)
	{
		SetClassW32(SubClassName);
		SubClass = LWindowsClass::Create(SubClassName); // Owned by the LWindowsClass object
	}
	Pos.ZOff(10, 10);
}

LControl::~LControl()
{
	if (SetOnDelete)
		*SetOnDelete = true;
}

LMessage::Result LControl::OnEvent(LMessage *Msg)
{
	LMessage::Result Status = 0;
	
	// Pre-OS event handler
	switch (Msg->m)
	{
		case WM_CREATE:
		{
			SetId(GetId());
			OnCreate();
			break;
		}
		case WM_SETTEXT:
		{
			if (IsWindowUnicode(_View))
				LBase::NameW((char16*)Msg->b);
			else
				LBase::Name((char*)Msg->b);
			break;
		}
		case WM_GETDLGCODE:
		case WM_NOTIFY:
		case WM_COMMAND:
		case WM_SYSKEYDOWN:
		case WM_SYSKEYUP:
		case WM_KEYDOWN:
		case WM_KEYUP:
		{
			bool Deleted = false;
			SetOnDelete = &Deleted;
			Status = LView::OnEvent(Msg);
			if (Deleted)
				return Status;
			
			SetOnDelete = NULL;
			break;
		}
		case WM_CTLCOLOREDIT:
		case WM_CTLCOLORSTATIC:
		{
			// These should never be called.. but just in case.
			return LView::OnEvent(Msg);
			break;
		}
		case WM_NCDESTROY:
		{
			Status = LView::OnEvent(Msg);
			break;
		}
	}
	
	// OS event handler
	if (SubClass)
		Status = SubClass->CallParent(Handle(), Msg->m, Msg->a, Msg->b);

	return Status;
}

LPoint LControl::SizeOfStr(const char *Str)
{
	LPoint Pt(0, 0);
	if (Str)
	{
		for (const char *s=Str; s && *s; )
		{
			const char *e = strchr(s, '\n');
			if (!e) e = s + strlen(s);

			LDisplayString ds(LSysFont, (char*)s, e - s);

			Pt.y += ds.Y();
			Pt.x = max(Pt.x, ds.X());

			s = (*e=='\n') ? e + 1 : 0;
		}
	}

	return Pt;
}

