/******************************************************************************
 *
 * 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.
 *
 *
 *	Composite.cpp
 *
 *****************************************************************************/

// SOL++2000
// 2000.05.08 Modified moveThumb method to initialize SCROLLINFO structure.
// 2000.05.09 Modified setScrollRange method.

#include <sol\Composite.h>
#include <sol\Application.h>
#include <sol\ScrollBar.h>
#include <shellapi.h>	
#include <sol\stdio.h>
#include <sol\Button.h>
#include <sol\ListBox.h>
#include <sol\ComboBox.h>
#include <sol\Text.h>
#include <sol\stdio.h>


#define Max(a,b)    (((a) > (b)) ? (a) : (b))
#define Min(a,b)    (((a) < (b)) ? (a) : (b))


int Composite::HORIZONTAL = (int)SB_HORZ;
int Composite::VERTICAL   = (int)SB_VERT;


int Composite::moveThumb(Event& event, int type)
{
	int pos  = getScrollPos(type);
	int prev = pos;

	SCROLLINFO scInfo;
	//<modified date="2000.05.08">
	memset(&scInfo, 0, sizeof(scInfo));
	scInfo.cbSize = sizeof(scInfo);
	//</modified>
	scInfo.fMask = SIF_ALL;

	int min, max;
	getScrollRange(type, &min, &max);
	int range = max - min;

	int request = LOWORD(event.getWParam());

    switch(request) {

	case SB_PAGEDOWN: pos += pageIncrement;
	case SB_LINEDOWN: pos = Min(range, pos + lineIncrement);
		break;

	case SB_PAGEUP:  pos -= pageIncrement;
	case SB_LINEUP:  pos = Max(0, pos - lineIncrement);
		break;

	case SB_TOP: pos = 0;
		break;

	case SB_BOTTOM:	pos = range;
		break;

	case SB_THUMBPOSITION:
		getScrollInfo(type, &scInfo);
		pos = scInfo.nTrackPos;
		break;

	case SB_THUMBTRACK:
		if(enableThumbTrack) {
			getScrollInfo(type, &scInfo);
			pos = scInfo.nTrackPos;
		}
		else {
			return 0;
		}
		break;

	default:
		break;
    	}
	setScrollPos(type, pos);
	return  prev - pos;
}


void Composite::doHorizScroll(Action& action)
{
	int diff = (int)action.getValue();
	scroll(diff, 0, NULL, NULL);
	update();
}


void Composite::doVertScroll(Action& action)
{
	int diff = (int)action.getValue();
	scroll(0, diff, NULL, NULL);
	update();
}


long Composite::horizScroll(Event& event)
{
	HWND hwnd = (HWND)event.getLParam();

	int  diff = 0;

	if(hwnd) { // scrollbar control.
		ScrollBar* scb = (ScrollBar*)Application::lookup(hwnd);
		if(scb)	diff = scb -> moveThumb(event);
	}
	else {	   // standard scrollbar
		hwnd = getWindow();
		diff = moveThumb(event, SB_HORZ);
	}
	callCallback(XmNhorizScrollCallback, (Key)hwnd, (void*)diff, event);
	return NULL;
}


long Composite::vertScroll(Event& event)
{
	HWND hwnd = (HWND)event.getLParam();

	int  diff = 0;

	if(hwnd) {// scrollbar control.
		ScrollBar* scb = (ScrollBar*)Application::lookup(hwnd);
		if(scb)	diff = scb -> moveThumb(event);
	}
	else {	  // standard scrollbar
		hwnd = getWindow();
		diff = moveThumb(event, SB_VERT);
	}
	callCallback(XmNvertScrollCallback, (Key)hwnd, (void*)diff, event);
	return NULL;
}


long Composite::command(Event& event) 
{
	WORD menuId = (WORD)event.getMenuId();
	HWND child  = event.getChild();

	//	Printf("Composite::Command \r\n");
	if(event.isMenuSelected() == TRUE ||
	   event.fromAccelerator() == TRUE) {
		// menu or accelarator has been selected. 
		callCallback(XmNmenuCallback, (Key)menuId,
				NULL,event);
	}
	else {
		View* view = (View*)Application::lookup(child);
		const char* name = XmNactivateCallback;
		if(view) {
			name = view->getCallbackName(event);
		}
	//	Printf("Composite::Command %s\r\n", name);
		callCallback(name, (Key)child, NULL, event);
	}

	// Return a result of callback.
	return event.getResult();
}


// Common notification from common controls.
const char* Composite::isCommonNotification(Event& event)
{
	static 
	Arg table[] = {
	{XmNerrorSpaceCallback, NM_OUTOFMEMORY},
	{XmNclickCallback,		NM_CLICK},
	{XmNdoubleClickCallback,NM_DBLCLK},
	{XmNreturnCallback,		NM_RETURN},
	{XmNrightClickCallback,		  NM_RCLICK},
	{XmNrightDoubleClickCallback, NM_RDBLCLK},
	{XmNsetFocusCallback,	      NM_SETFOCUS},
	{XmNkillFocusCallback,	NM_KILLFOCUS},
	};

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


// EventHandler for message WM_NOTIFY.
long Composite::notify(Event& event) 
{
	NMHDR* hdr  = (NMHDR*)event.getLParam();
	HWND sender  = hdr->hwndFrom;

	View* view = (View*)Application::lookup(sender);
	const char* name = NULL;
	
	if(view) {
		name = isCommonNotification(event);
		if(name == NULL) {
			name = view->getCallbackName(event);
		}
		if(name) {
			callCallback(name, (Key)sender, NULL, event);
		}
	}
	// Return a result of callback.
	return event.getResult();
}



// If XmNhorizScrollBar is TRUE, then set WS_HSCROLL style, 
// and so on.

Arg Composite::styles[] = {
	{XmNhorizScrollBar, WS_HSCROLL},
	{XmNvertScrollBar,  WS_VSCROLL},
};


// Used for Modeless and Modal Dialog.
Composite::Composite(View* parent)
	:View(parent),
		hicon(null),
		hfocus(null),
		pageIncrement(16),
		lineIncrement(2),
		enableThumbTrack(False),
		layoutManager(null)

{
	addEventHandler(WM_COMMAND, this, 
	       (Handler)&Composite::command, NULL);
	addEventHandler(WM_NOTIFY, this, 
	       (Handler)&Composite::notify, NULL);

	addEventHandler(WM_HSCROLL, this,
		(Handler)&Composite::horizScroll, NULL);
	addEventHandler(WM_VSCROLL, this,
		(Handler)&Composite::vertScroll, NULL);

	addEventHandler(WM_SIZE, this,
		(Handler)&Composite::size, NULL);

}


// 2001/03/11
// Used for Modeless and Modal Dialog.
Boolean Composite::create(View* parent)
{
	Boolean rc = View::create(parent);

	hicon		= null;
	hfocus		= null;
	pageIncrement = 16;
	lineIncrement = 2;
	enableThumbTrack =False;
	layoutManager = null;

	addEventHandler(WM_COMMAND, this, 
	       (Handler)&Composite::command, NULL);
	addEventHandler(WM_NOTIFY, this, 
	       (Handler)&Composite::notify, NULL);

	addEventHandler(WM_HSCROLL, this,
		(Handler)&Composite::horizScroll, NULL);
	addEventHandler(WM_VSCROLL, this,
		(Handler)&Composite::vertScroll, NULL);

	addEventHandler(WM_SIZE, this,
		(Handler)&Composite::size, NULL);

	return rc;
}


Composite::Composite(View* parent, const char* name, Args& args)
	:View(parent, name, args.set(styles, XtNumber(styles))),
		hicon(null),
		hfocus(null),
		pageIncrement(16),
		lineIncrement(2),
		enableThumbTrack(False),
		layoutManager(null)
{
	addEventHandler(WM_COMMAND, this, 
	       (Handler)&Composite::command, NULL);
	addEventHandler(WM_NOTIFY, this, 
	       (Handler)&Composite::notify, NULL);

	addEventHandler(WM_HSCROLL, this,
		(Handler)&Composite::horizScroll, NULL);
	addEventHandler(WM_VSCROLL, this,
		(Handler)&Composite::vertScroll, NULL);
	addEventHandler(WM_ACTIVATE, this,
		(Handler)&Composite::activate, NULL);

	addEventHandler(WM_SIZE, this,
		(Handler)&Composite::size, NULL);

	setValues(args);

	addCallback(XmNhorizScrollCallback, (Key)getWindow(), this,
		(Callback)&Composite::doHorizScroll, NULL);
	addCallback(XmNvertScrollCallback, (Key)getWindow(), this,
		(Callback)&Composite::doVertScroll,  NULL);


}


Composite::~Composite()
{
	//Don't delete the layoutManager;

	if(hicon) {
		::DestroyIcon(hicon);
	}
}

Boolean Composite::create(View* parent, const char* name, Args& args)
{
	//<modified date="2001/03/11">
	Boolean rc = View::create(parent, name, args.set(styles, XtNumber(styles)));

	hicon   = null;
	hfocus	= null;
	pageIncrement = 16;
	lineIncrement = 2;
	enableThumbTrack = False;
	layoutManager = null;
	//</modified>

	addEventHandler(WM_COMMAND, this, 
	       (Handler)&Composite::command, NULL);
	addEventHandler(WM_NOTIFY, this, 
	       (Handler)&Composite::notify, NULL);

	addEventHandler(WM_HSCROLL, this,
		(Handler)&Composite::horizScroll, NULL);
	addEventHandler(WM_VSCROLL, this,
		(Handler)&Composite::vertScroll, NULL);
	addEventHandler(WM_ACTIVATE, this,
		(Handler)&Composite::activate, NULL);

	addEventHandler(WM_SIZE, this,
		(Handler)&Composite::size, NULL);

	setValues(args);

	addCallback(XmNhorizScrollCallback, (Key)getWindow(), this,
		(Callback)&Composite::doHorizScroll, NULL);
	addCallback(XmNvertScrollCallback, (Key)getWindow(), this,
		(Callback)&Composite::doVertScroll,  NULL);
	return rc;
}


long Composite::activate(Event& event)
{
	if(LOWORD(event.getWParam()) == WA_INACTIVE) {
		hfocus = getFocus();
	}
	else if (hfocus) {
		setFocus(hfocus);	
	}
	return NULL;
}


void Composite::layout(int x, int y, int w, int h)
{
	if (getParent() != null) {
		reshape(x, y, w, h);
	}
	if (layoutManager) {
		layoutManager->layout(0, 0, w, h);
	}
}


long Composite::size(Event& event) 
{
	int w, h;
	event.getSize(w, h);
	int x, y;
	getLocation(x, y);

	layout(x, y, w, h);
	return 0L;
}


void Composite::setValues(Args& args)
{
	View::setValues(args);

	updateStyle(args, styles, XtNumber(styles));

	ulong  val;
	if(args.get(XmNpageIncrement, &val))
		pageIncrement = (int)val;
	if(args.get(XmNlineIncrement, &val))
		lineIncrement = (int)val;

	if(args.get(XmNenableThumbTrack, &val))
		enableThumbTrack = (BOOL)val;


	int max, min;
	int type = SB_HORZ;
	getScrollRange(type, &min, &max);
	if(args.get(XmNhorizScrollMaximum, &val))
		setScrollRange(type, min, (int)val);
	getScrollRange(type, &min, &max);
	if(args.get(XmNhorizScrollMinimum, &val))
		setScrollRange(type, (int)val, max);

	type = SB_VERT;
	getScrollRange(type, &min, &max);
	if(args.get(XmNvertScrollMaximum, &val))
		setScrollRange(type, min, (int)val);
	getScrollRange(type, &min, &max);
	if(args.get(XmNvertScrollMinimum, &val))
		setScrollRange(type, (int)val, max);

	if(args.get(XmNhorizThumbPosition, &val))
		setScrollPos(SB_HORZ, (int)val);
	if(args.get(XmNvertThumbPosition, &val))
		setScrollPos(SB_VERT, (int)val);

	// Extract an icon from *.ico or *.exe and set it.
	// CreateIcon API will be called;

	if(args.get(XmNiconFile, &val)) {
		char* name  = (char*)val; // file name
		get(XmNinstance, &val);
		HICON temp = ::ExtractIcon((HINSTANCE)val, name, 0);
		if(temp) {
			setClassLong(GCL_HICON, (LONG)temp);
			if(hicon) ::DestroyIcon(hicon);
			hicon = temp;
		}
	}
	// Get an icon handle and set it.
	if(args.get(XmNwindowIcon, &val)) {
		setClassLong(GCL_HICON, (LONG)val);
	}
}


void	Composite::realize()
{
	show(SW_SHOWNORMAL);
}


const char* Composite::findCallbackName(Event& event, HWND child)
{
	char name[128];
	::GetClassName(child, name, sizeof(name));

	if(strcmp(name, "Button") == 0) {
		Button button(NULL, child);
		return button.getCallbackName(event);
	}	
	if(strcmp(name, "Edit") == 0) {
		Text  text(NULL, child);
		return text.getCallbackName(event);
	}	
	if(strcmp(name, "ListBox") == 0) {
		ListBox listbox(NULL, child);
		return listbox.getCallbackName(event);
	}	
	if(strcmp(name, "ComboBox") == 0) {
		ComboBox combobox(NULL, child);
		return combobox.getCallbackName(event);
	}
// Add lines here for new controls. 
//
	return XmNactivateCallback;		
}


void  Composite::getValues(Args& args)
{
	View::getValues(args);
	getStyle(args, styles, XtNumber(styles));

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

	for(int i = 0; i<num; i++) {
		const char*    name = arg[i].name;
		ulong* val  = (ulong*)arg[i].value;
		if(name == XmNpageIncrement) {
			*val =  lineIncrement;
			continue;
		}
		if(name == XmNlineIncrement) {
			*val =  lineIncrement;
			continue;
		}
		if(name == XmNhorizScrollMaximum) {
			getScrollRange(SB_HORZ, &min, &max);
			*val = max;
			continue;
		}
		if(name == XmNhorizScrollMinimum) {
			getScrollRange(SB_HORZ, &min, &max);
			*val = min;
			continue;
		}
		if(name == XmNhorizThumbPosition) {
			*val = (ulong)getScrollPos(SB_HORZ);				
			continue;
		}
		if(name == XmNvertScrollMaximum) {
			getScrollRange(SB_VERT, &min, &max);
			*val = max;
			continue;
		}
		if(name == XmNvertScrollMinimum) {
			getScrollRange(SB_VERT, &min, &max);
			*val = min;
			continue;
		}
		if(name == XmNvertThumbPosition) {
			*val = (ulong)getScrollPos(SB_VERT);				
			continue;
		}
	}
}


void Composite::setScrollExtent(UINT w, UINT h)
{
	extent.set(w, h);
	justifyScrollRange();
}


void Composite::justifyScrollRange()
{
	RECT r;
	getClientRect(&r);
	int maxx, maxy;
	UINT w, h;
	extent.get(&w, &h); 
	maxx = w - (r.right - r.left);
	maxy = h - (r.bottom - r.top);
	if(maxx < 0) maxx = 0;
	//<modified date="2000.05.09">
	//if(maxx < 0) maxy = 0;
	if(maxy < 0) maxy = 0;
	//</modified>

	setScrollRange(SB_HORZ, 0, maxx);
	setScrollRange(SB_VERT, 0, maxy);

	// Justify
	getClientRect(&r);
	maxx = w - (r.right - r.left);
	maxy = h - (r.bottom - r.top);
	if(maxx < 0) maxx = 0;
	//<modified date="2000.05.09">
	//if(maxx < 0) maxy = 0;
	if(maxy < 0) maxy = 0;
	//</modified>

	setScrollRange(SB_HORZ, 0, maxx);
	setScrollRange(SB_VERT, 0, maxy);
}



BOOL Composite::getScrollInfo(int type, SCROLLINFO* scInfo) 
{
	BOOL rc = FALSE;
	if(scInfo) {
		scInfo->cbSize = sizeof(SCROLLINFO);
		rc  = ::GetScrollInfo(getWindow(), type, scInfo);
	}
	return rc;
}
	
int	Composite::setScrollInfo(int type, SCROLLINFO* scInfo) 
{
	int pos = 0;
	if(scInfo) {
		scInfo->cbSize = sizeof(SCROLLINFO);
		pos = ::SetScrollInfo(getWindow(), type, scInfo, TRUE);
	}
	return pos;
}


void Composite::getPreferredSize(Dimension& d)
{
	if (layoutManager == null) {
		View::getPreferredSize(d);
	}
	else {
		int w, h;
		getSize(w, h);
		layoutManager -> getExtension(d, w, h);
	}
}


void Composite::pack()
{
	int w, h;
	getSize(w, h);
	send(WM_SIZE, 0, MAKELONG(w, h));
}
