/******************************************************************************
 *
 * Copyright (c) 1999	TOSHIYUKI ARAI. ALL RIGHTS RESERVED. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *  
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *	View.cpp
 *
 *****************************************************************************/

// SOL++2000
// 2000.02.18

#include <sol\View.h>
#include <sol\Application.h>
#include <sol\stdio.h>
#include <sol\Font.h>

#include <sol\InvalidWindowException.h>


long View::destroy(Event& event)
{
	::PostQuitMessage(0);
	return 0L;
}


long View::defaultProc(Event& event)
{
	return ::DefWindowProc(getWindow(), event.getMessage(),
				   event.getWParam(),
				   event.getLParam());
}


View::View()
	:parent(null),
	font(null),
	editable(False),
	destructable(True),
	preferredSize(100, 100)
{
}


View::View(View* parview)
	:parent(parview),
	font(null),
	editable(False),
	destructable(True),
	preferredSize(100, 100)
{
	//
}


// 2001/03/11
Boolean View::create(View* parview)
{
	parent	= parview;
	font	= null;
	editable = False;
	destructable = True;
	Dimension d(100,100);
	preferredSize.set(d);
	return True;
}


View::View(View* parview, const char* name, Args& args)
	:parent(parview),
	font(null),
	editable(False),
	destructable(True),
	preferredSize(100, 100)
{
	Boolean rc = create(parview, name, args);
	if (rc == False) {
		throw InvalidWindowException("Failed to create a window");
	}
}


View::~View()
{
	HWND hwnd = getWindow();
	if(isWindow() && destructable) {
		destroyWindow();
	}
	Application::remove(hwnd);
}


void View::addCallback(const char* name, Object* object,
		       Callback proc, void* data)
{
	if(parent) {
		parent -> addCallback(name, (const Key)getWindow(), object, 
							proc, data);
	}
}


void View::addCallback(const char* name, const Key key, Object* object,
		       Callback proc, void* data)
{
	callbackList.add(name, key, object, proc, data);
}


void View::addEventHandler(UINT message, Object* object,
		           Handler proc, void* data)
{
	handlerList.add(message, object, proc, data);
}


Boolean View::create(View* parview, const char* name, Args& args)
{
	Boolean rc = False;

	parent = parview;
	font   = null;
	editable = False;
	destructable = True;

	// If a window were created, return False.
	if (getWindow()) {
		return rc;
	}

	if (args.get(XmNdialogUnit)) {
		args.toPixelUnit();
	}


	char* className = (char*)args.get(XmNclassName);
	if (className == NULL) {
		className = "View";
		args.set(XmNclassName, (ulong)className);
	}

	if (args.get(XmNpredefined) == False){
		Application::registerClass(args);
	}

	HWND hparent = NULL;
	if (parent) {
		hparent = parent->getWindow();
	}

	HWND hwnd = ::CreateWindowEx((DWORD)args.get(XmNexStyle),
			    className, name,
			    (DWORD)args.get(XmNstyle),
				(int) args.get(XmNx),
				(int) args.get(XmNy),
				(int) args.get(XmNwidth),
				(int) args.get(XmNheight),
			    hparent, NULL,
			    Application::getInstance(),
			    (LPSTR)args.get(XmNparam));

	if(hwnd) {
		setWindow(hwnd);

		Application::add(hwnd, this);
		setViewId((int)args.get(XmNid));

		if(parent == NULL) {
			addEventHandler(WM_DESTROY, this,
         	(Handler)&View::destroy, NULL);
   		}
		rc = True;
	}
	else {
	 	::MessageBox(NULL, "SOL++: Failed to CreateWindow",
	 			className, (int)MB_OK);
	}
	return rc;
}


long View::dispatch(Event& event)
{
	ulong value = NULL;
 
	if(handlerList.call(event, &value)) {
		return value;
	}
	return defaultProc(event);
}


void View::callCallback(const char* name, const Key key, 
			void* value, Event& event)
{
	callbackList.call(name, key, value, event);
}



ulong View::get(const char* name)
{
	ulong val;
	Args args;
	args.set(name, (ulong)&val);
	getValues(args);
	return val;
}


void View::get(const char* name, ulong* val)
{
	Args args;
	args.set(name, (ulong)val);
	getValues(args);
}

Arg*  View::getCallbackTable(int* num)
{
	*num = 0;
	return NULL;
}


const char* View::getCallbackName(Event& event)
{
	int  num = 0;
	Arg* table = NULL;

	table = getCallbackTable(&num);

	ulong val = (ulong)event.getNotification();
	const char* name = XmNactivateCallback;
	for(int i = 0; i<num; i++) {
		if(table[i].value == val) {
			name = table[i].name;
			break;					
		} 
	}
	return name;
}


void	View::getValues(Args& args)
{
	int x, y, w, h;
	getGeometry(x, y, w, h);

	Arg ar[4];
	XtSetArg(ar[0], XmNx,     x);
	XtSetArg(ar[1], XmNy,     y);
	XtSetArg(ar[2], XmNwidth, w);
	XtSetArg(ar[3], XmNheight,h);

	HINSTANCE ins = getInstanceHandle();

	int num   = args.getCount();
	Arg* arg  = args.getArgList();

	LONG style   = getWindowLong(GWL_STYLE);
	LONG exStyle = getWindowLong(GWL_EXSTYLE);
	for(int j = 0; j<num; j++) {
		const char*   name  = arg[j].name;
		ulong* val  = (ulong*)arg[j].value;
		if(name == XmNstyle) {
			*val = style;
			continue;
		}
		if(name == XmNexStyle) {
			*val = exStyle;
			continue;
		}
		if(name == XmNid) {
			*val = getViewId();
			continue;
		}
		if(name == XmNinstance) {
			*val = (ulong)ins;
			continue;
		}
		if(name == XmNgroup) {
			*val = False;
			if(style & WS_GROUP) 
				*val = True;
			continue;
		}
		if(name == XmNacceptFiles) {
			*val = False;
			if(exStyle & WS_EX_ACCEPTFILES)
				*val = True;
			continue;
		}
	
		if(name == XmNlabelString) {
			int len = getTextLength();
			char* buff = new char[len+1];
			getText(buff, len+1);
			*val = (ulong)buff;
		}
		for(int i = 0; i<4; i++) {
			if(name == ar[i].name) {
			 	*val = (ulong)ar[i].value;
			}
		}
	}
}


void View::getGeometry(int& x, int& y, int& width, int& height)
{
	RECT r;
	getWindowRect(&r);
	x 	= r.left;
	y 	= r.top;
	width  = r.right  - r.left;
	height = r.bottom - r.top;

	if(parent) {
		POINT p;
		p.x = x;
		p.y = y;
		parent -> toClient(&p);
		x = p.x;
		y = p.y;
	}
}

void View::getSize(int& width, int& height)
{
	RECT r;
	getWindowRect(&r);
	width  = r.right  - r.left;
	height = r.bottom - r.top;
}

void View::enable()
{
	show(SW_HIDE);
	enableWindow(True);
	show(SW_NORMAL);
}


void View::disable()
{
	show(SW_HIDE);
	enableWindow(False);
	show(SW_NORMAL);
}


void  View::getNormalPlacement(RECT* r)
{
	WINDOWPLACEMENT pl;
	pl.length = sizeof(WINDOWPLACEMENT);
	pl.showCmd = SW_SHOWNORMAL;
	getPlacement(&pl);
	*r = pl.rcNormalPosition;
}

void View::getLocation(int& x, int& y)
{
	int width, height;
	getGeometry(x, y, width, height);
}


void View::layout(int x, int y, int w, int h)
{
	reshape(x, y, w, h);
}


long CALLBACK View::procedure(HWND hwnd, UINT message, 
			WPARAM wParam, LPARAM lParam)
{
	Event event(message, wParam, lParam);

	View* view = Application::lookup(hwnd);

	if (view && view->isWindow()) {
		return view -> dispatch(event);
	}
	return ::DefWindowProc(hwnd, message, wParam, lParam);
}


void View::removeEventHandler()
{
	handlerList.clear();
}


void View::setViewId(int id)
{
	setId(id);
	DWORD style = getWindowLong(GWL_STYLE);
	if(style & WS_CHILD) {
		setWindowLong(GWL_ID, id);
	}
}

void View::setLocation(int x, int y)
{
	int xx, yy, width, height;

	getGeometry(xx, yy, width, height);
	reshape(x, y, width, height);
	update(NULL);
}


void View::setSize(int w, int h)
{
	int xx, yy, width, height;

	getGeometry(xx, yy, width, height);
	reshape(xx, yy, w, h);
	update(NULL);
}


void View::setVisible(Boolean flag) 
{
	if (flag) 
		map();
	else 
		unmap();
}

void View::set(const char* name, ulong val)
{
	Args args;
	args.set(name, val);
	setValues(args);
}



void	View::setValues(Args& args)
{
	int x, y, w, h;

	getGeometry(x, y, w, h); 
	Arg ar[4];
	XtSetArg(ar[0], XmNx,     x);
	XtSetArg(ar[1], XmNy,     y);
	XtSetArg(ar[2], XmNwidth, w);
	XtSetArg(ar[3], XmNheight,h);

	Boolean mustmove = False;
	int num  = args.getCount();
	Arg* arg = args.getArgList();
	int i, j;
	for(j = 0; j<num; j++) {
		for(i = 0; i<4; i++) {
			if(ar[i].name == arg[j].name) {
			 	ar[i].value = arg[j].value;
				mustmove = True;
			}
		}
	}
	Boolean config = (Boolean)args.get(XmNnoConfiguration);
	if(!config) {
	    if(mustmove && (ar[2].value != (ulong)CW_USEDEFAULT) ||
                       (ar[3].value != (ulong)CW_USEDEFAULT)) {
			reshape((int)ar[0].value, (int)ar[1].value,
	                 (int)ar[2].value, (int)ar[3].value);
	     }
	}
	ulong val;
	LONG style   = getWindowLong(GWL_STYLE);
	LONG exStyle = getWindowLong(GWL_EXSTYLE);

	if(args.get(XmNgroup, &val)) {
		if(val) {
			style |= WS_GROUP;
		}
		else {
			if(style & WS_GROUP) 
				style ^= WS_GROUP;
		}
		setWindowLong(GWL_STYLE, style);
	}

	if(args.get(XmNacceptFiles, &val)) {
		if(val) {
			exStyle |= WS_EX_ACCEPTFILES;
		}
		else {
			if(exStyle & WS_EX_ACCEPTFILES)
				exStyle ^= WS_EX_ACCEPTFILES;
		}
		setWindowLong(GWL_EXSTYLE, exStyle);
	}
	if(args.get(XmNlabelString, &val)) {
		setText((char*)val);
	}

	if(args.get(XmNid, &val)) {
		setViewId((int)val);
	}
}


void View::refresh(RECT* rect)
{
	invalidate(rect);
	update();
}

void View::refresh(int x, int y, int width, int height)
{
	RECT rect;
	rect.left   = x;
	rect.top    = y;
	rect.right  = x+width;
	rect.bottom = y+height;
	invalidate(&rect);
	update();
}


void View::updateStyle(Args& args, Arg* arg, int num)
{
	for(int i = 0; i<num; i++) {
		DWORD style = (DWORD)getWindowLong(GWL_STYLE);
		ulong val;
		DWORD st = (DWORD)arg[i].value;
		if(args.get(arg[i].name, &val)) {
			if(val) {
				style |= st;
			}
			else {
				if(style & st)
					style ^= st;
			}
			setWindowLong(GWL_STYLE, style);
		}
	}
}


void View::getStyle(Args& args, Arg* arg, int num)
{
	DWORD style = (DWORD)getWindowLong(GWL_STYLE);

	for(int i = 0; i<num; i++) {
		ulong* val = (ulong*)args.get(arg[i].name);
		if(val) {
			*val = False;
			if(style & (DWORD)arg[i].value)
				*val = True;
		}
	}
}


void View::setFont(Font* font)
{
	this->font = font;
	if(font) {
		send(WM_SETFONT, (WPARAM)font->getFont(),
					MAKELPARAM(True,0));
	}
}

void View::setFont(Font& font1)
{
	font = &font1;
	if(font) {
		send(WM_SETFONT, (WPARAM)font->getFont(),
					MAKELPARAM(True,0));
	}
}

void View::setSysCursor(const char* name)
{
	::SetCursor(::LoadCursor(NULL, name));		
}


void View::getPreferredSize(Dimension& d) 
{
	preferredSize.get(d);
}


void View::setPreferredSize(Dimension& d) 
{
	setSize(d.getWidth(), d.getHeight());
	preferredSize.set(d);
}

