/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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 3 of the License, 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, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//																			//
//	pfGUIListBoxMod Definition												//
//																			//
//////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "pfGUIListBoxMod.h"
#include "pfGUIListElement.h"
#include "pfGameGUIMgr.h"
#include "pfGUIUpDownPairMod.h"
#include "pfGUIControlHandlers.h"
#include "pfGUIDialogMod.h"

#include "../pnMessage/plRefMsg.h"
#include "../pfMessage/pfGameGUIMsg.h"
#include "../plMessage/plAnimCmdMsg.h"
#include "../plAvatar/plAGModifier.h"
#include "../plGImage/plDynamicTextMap.h"
#include "../plInputCore/plInputInterface.h"
#include "plgDispatch.h"
#include "hsResMgr.h"


#define kIndentAmount		16


//// plSmallRect Stuff ///////////////////////////////////////////////////////

void	pfGUIListBoxMod::plSmallRect::Set( Int16 l, Int16 t, Int16 r, Int16 b ) 
{
	fLeft = l;
	fTop = t;
	fRight = r; 
	fBottom = b; 
}

hsBool	pfGUIListBoxMod::plSmallRect::Contains( Int16 x, Int16 y )
{
	if( x < fLeft || x > fRight || y < fTop || y > fBottom )
		return false;

	return true;
}

//// Wee Little Control Proc for scrolling ///////////////////////////////////

class pfScrollProc : public pfGUICtrlProcObject
{
	protected:

		pfGUIListBoxMod	*fParent;

	public:

		pfScrollProc( pfGUIListBoxMod *parent ) : fParent( parent ) {}

		virtual void	DoSomething( pfGUIControlMod *ctrl )
		{
			// Do a check here to make sure we actually changed scroll
			// positions--if not, we don't want to update, since that'll be 
			// slow as hell
			int newScrollPos = (int)fParent->fScrollControl->GetMax() - (int)fParent->fScrollControl->GetCurrValue();
			if( newScrollPos != fParent->fScrollPos )
			{
				fParent->IUpdate();
				fParent->HandleExtendedEvent( pfGUIListBoxMod::kScrollPosChanged );
			}
		}
};

//// Constructor/Destructor //////////////////////////////////////////////////

pfGUIListBoxMod::pfGUIListBoxMod()
{
	SetFlag( kWantsInterest );
	fCurrClick = -1;
	fCurrHover = -1;
	fModsAtDragTime = 0;
	fScrollControl = nil;
	fCheckScroll = false;
	fSingleSelElement = -1;
	fScrollRangeUpdateDeferred = false;
	fScrollPos = 0;
	fLocked = false;
	fReadyToRoll = false;
	fClicking = false;

	fScrollProc = nil;
}

pfGUIListBoxMod::~pfGUIListBoxMod()
{
	ClearAllElements();

	if( fScrollProc && fScrollProc->DecRef() )
		delete fScrollProc;
}

//// IEval ///////////////////////////////////////////////////////////////////

hsBool	pfGUIListBoxMod::IEval( double secs, hsScalar del, UInt32 dirty )
{
	return pfGUIControlMod::IEval( secs, del, dirty );
}

//// MsgReceive //////////////////////////////////////////////////////////////

hsBool	pfGUIListBoxMod::MsgReceive( plMessage *msg )
{
	plGenRefMsg	*refMsg = plGenRefMsg::ConvertNoRef( msg );
	if( refMsg != nil )
	{
		if( refMsg->fType == kRefScrollCtrl )
		{
			if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) )
			{
				fScrollControl = pfGUIValueCtrl::ConvertNoRef( refMsg->GetRef() );
				fScrollControl->SetHandler( fScrollProc );
				fScrollControl->SetStep( 1.f );
				ICalcScrollRange();
			}
			else
				fScrollControl = nil;
			return true;
		}
		else if( refMsg->fType == kRefDynTextMap )
		{
			// Capture this so we can reset our flag, but do NOT just return from it!
			if( !( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) ) )
				fReadyToRoll = false;
		}
	}

	return pfGUIControlMod::MsgReceive( msg );
}

//// IPostSetUpDynTextMap ////////////////////////////////////////////////////

void	pfGUIListBoxMod::IPostSetUpDynTextMap( void )
{
	ICalcWrapStarts();
	ICalcScrollRange();
	fReadyToRoll = true;
}

//// IUpdate /////////////////////////////////////////////////////////////////

void	pfGUIListBoxMod::IUpdate( void )
{
	int			i, j;
	UInt16		x, y, width, height, maxHeight;


	if( !fReadyToRoll )
		return;
	if( fScrollRangeUpdateDeferred )
	{
		fScrollRangeUpdateDeferred = false;
		ICalcScrollRange();
	}

	fDynTextMap->ClearToColor( GetColorScheme()->fBackColor );
	if( fElements.GetCount() == 0 )
	{
		fDynTextMap->FlushToHost();
		return;
	}

	if( fLocked )
	{
		hsStatusMessage( "WARNING: Forcing unlock on GUI list box due to call to IUpdate()\n" );
		UnlockList();
	}

	if( fScrollControl != nil )
	{
		// We reverse the value, since we want the "up" button to scroll "up", which actually
		// *decreases* the scrollPos
		fScrollPos = (int)fScrollControl->GetMax() - (int)fScrollControl->GetCurrValue();

		if( fCheckScroll )
		{
			// If this is set, we just did something to change the selection to a single item.
			// Since it makes sense to try to ensure this item is on the screen, we thus do so here.
			// We don't want to do this every time, because we could be simply clicking on the
			// scroll bars, in which case we DON'T want to do this check, since it would be
			// fighting against the user, which is a big UI design no-no.
			// To do this check, we search on either side of our scroll range (we can only
			// search past after we draw, unfortunately). If there's a selected item out
			// of our range, then we best move the scrollPos to move it into view
			for( j = 0; j < fScrollPos; j++ )
			{
				int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount();

				hsBool anySelected = false;
				for( i = fWrapStartIdxs[ j ]; i < end; i++ )
					anySelected |= fElements[ i ]->IsSelected();

				if( anySelected )
				{
					// Shit. Move the scrollPos up to this item at the very least
					fScrollPos = j;
					fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) );
					fCheckScroll = false;
					break;
				}
			}
		}
	}
	else
		fScrollPos = 0;

	fElementBounds.SetCountAndZero( fElements.GetCount() );

	if( !HasFlag( kScrollLeftToRight ) )
	{
		for( j = fScrollPos, y = 4; j < fWrapStartIdxs.GetCount() && y < fDynTextMap->GetVisibleHeight() - 8; j++ )
		{
			int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount();

			for( maxHeight = 0, i = fWrapStartIdxs[ j ], x = 0; i < end; i++ )
			{
				if( HasFlag( kGrowLeavesAndProcessOxygen ) && fElements[ i ]->IsCollapsed() )
					continue;		// Skip collapsed items

				fElements[ i ]->GetSize( fDynTextMap, &width, &height );

				if( !HasFlag( kAllowMultipleElementsPerRow ) )
					width = fDynTextMap->GetVisibleWidth();

				if( y + height >= fDynTextMap->GetVisibleHeight() - 8 )
					height = fDynTextMap->GetVisibleHeight() - 8 - y;

				if( HasFlag( kGrowLeavesAndProcessOxygen ) )
				{
					x += fElements[ i ]->GetIndentLevel() * kIndentAmount;
					if( x + width > fDynTextMap->GetVisibleWidth() )
						width = fDynTextMap->GetVisibleWidth() - x;
				}

				if( !fElements[ i ]->Draw( fDynTextMap, x, y, width, height ) )
				{
					// Couldn't draw, so force it to be at the end of the scroll range
					y = fDynTextMap->GetVisibleHeight() - 8;
					j--;
					break;
				}

				maxHeight = ( height > maxHeight ) ? height : maxHeight;
				fElementBounds[ i ].Set( x, y, x + width - 1, y + height - 1 );
				x += width;
			}

			y += maxHeight;
		}
	}
	else
	{
		for( j = fScrollPos, x = 4; j < fWrapStartIdxs.GetCount() && x < fDynTextMap->GetVisibleWidth() - 8; j++ )
		{
			int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount();

			for( maxHeight = 0, i = fWrapStartIdxs[ j ], y = 0; i < end; i++ )
			{
				fElements[ i ]->GetSize( fDynTextMap, &width, &height );

				if( !HasFlag( kAllowMultipleElementsPerRow ) )
					height = fDynTextMap->GetVisibleHeight();

				if( x + width >= fDynTextMap->GetVisibleWidth() - 8 )
					width = fDynTextMap->GetVisibleWidth() - 8 - x;

				if( !fElements[ i ]->Draw( fDynTextMap, x, y, width, height ) )
				{
					// Couldn't draw, so force it to be at the end of the scroll range
					x = fDynTextMap->GetVisibleWidth() - 8;
					j--;
					break;
				}

				maxHeight = ( width > maxHeight ) ? width : maxHeight;
				fElementBounds[ i ].Set( x, y, x + width - 1, y + height - 1 );
				y += height;
			}

			x += maxHeight;
		}
	}

	// Second part of our scroll check--if we got here, then there was nothing selected
	// before the viewing area. So check the rest
	if( fCheckScroll && fScrollControl != nil )
	{
		fCheckScroll = false;
		for( ; j < fWrapStartIdxs.GetCount(); j++ )
		{
			int end = ( j < fWrapStartIdxs.GetCount() - 1 ) ? fWrapStartIdxs[ j + 1 ] : fElements.GetCount();

			hsBool anySelected = false;
			for( i = fWrapStartIdxs[ j ]; i < end; i++ )
				anySelected |= fElements[ i ]->IsSelected();

			if( anySelected )
			{
				fScrollPos = j;
				fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) );
				IUpdate();		// Gotta update again, since we just changed the scrollPos after the fact
				return;
			}
		}
	}

	fDynTextMap->FlushToHost();
}

//// ICalcWrapStarts /////////////////////////////////////////////////////////
//	Calculates the starting indexes for each row of items. Call whenever you
//	update the element list.

void	pfGUIListBoxMod::ICalcWrapStarts( void )
{
	UInt16		i, x, y, maxHeight, width, height;


	fWrapStartIdxs.Reset();

	if( HasFlag( kAllowMultipleElementsPerRow ) )
	{
		if( !HasFlag( kScrollLeftToRight ) )
		{
			for( i = 0, x = (UInt16)-1, y = 4, maxHeight = 0; i < fElements.GetCount(); i++ )
			{
				fElements[ i ]->GetSize( fDynTextMap, &width, &height );

				if( x + width >= fDynTextMap->GetVisibleWidth() )
				{
					x = 0;
					y += maxHeight;
					maxHeight = 0;
					fWrapStartIdxs.Append( i );
				}
				x += width;
				maxHeight = ( height > maxHeight ) ? height : maxHeight;
			}
		}
		else
		{
			for( i = 0, y = (UInt16)-1, x = 4, maxHeight = 0; i < fElements.GetCount(); i++ )
			{
				fElements[ i ]->GetSize( fDynTextMap, &width, &height );
				if( y + height >= fDynTextMap->GetVisibleHeight() )
				{
					y = 0;
					x += maxHeight;
					maxHeight = 0;
					fWrapStartIdxs.Append( i );
				}
				y += height;
				maxHeight = ( width > maxHeight ) ? width : maxHeight;
			}
		}
	}
	else
	{
		for( i = 0; i < fElements.GetCount(); i++ )
		{
			if( HasFlag( kGrowLeavesAndProcessOxygen ) && fElements[ i ]->IsCollapsed() )
				continue;

			fWrapStartIdxs.Append( i );
		}
	}
}

//// ICalcScrollRange ////////////////////////////////////////////////////////
//	Call whenever you update the element list

void	pfGUIListBoxMod::ICalcScrollRange( void )
{
	UInt16		ctrlHt, ctrlWd, height, width, maxHeight;
	int			i, j, end;


	if( !fReadyToRoll )
	{
		fScrollRangeUpdateDeferred = true;
		return;
	}

	// Basically, the scroll range is 0 to (numElements-1), but since if we scroll
	// that far, we won't see anything left except the last element, we have to calculate
	// how many elements away from the end we can get before we see blank space. Which means
	// counting up from the end until we have passed the height of our control.

	end = fElements.GetCount();
	if( !HasFlag( kScrollLeftToRight ) )
	{
		if( HasFlag( kGrowLeavesAndProcessOxygen ) )
		{
			// OK, that really isn't the end, the end will the count of non-collapsed elements. Haha.
			for( i = 0, end = 0; i < fElements.GetCount(); i++ )
			{
				if( !fElements[ i ]->IsCollapsed() )
					end++;
			}
		}

		for( i = fWrapStartIdxs.GetCount() - 1, height = 0; i >= 0; i-- )
		{
			// Get the max height of this row
			maxHeight = 0;
			for( j = fWrapStartIdxs[ i ]; j < end; j++ )
			{
				fElements[ j ]->GetSize( fDynTextMap, &width, &ctrlHt );
				maxHeight = ( ctrlHt > maxHeight ) ? ctrlHt : maxHeight;
			}
			end = fWrapStartIdxs[ i ];
			height += maxHeight;

			if( height > fDynTextMap->GetVisibleHeight() - 8 )
				break;
		}
	}
	else
	{
		for( i = fWrapStartIdxs.GetCount() - 1, width = 0; i >= 0; i-- )
		{
			// Get the max width of this column
			maxHeight = 0;
			for( j = fWrapStartIdxs[ i ]; j < end; j++ )
			{
				fElements[ j ]->GetSize( fDynTextMap, &ctrlWd, &ctrlHt );
				maxHeight = ( ctrlWd > maxHeight ) ? ctrlWd : maxHeight;
			}
			end = fWrapStartIdxs[ i ];
			width += maxHeight;

			if( width > fDynTextMap->GetVisibleWidth() - 8 )
				break;
		}
	}

	// Note: i went one past, so our scroll range is really 0 to i+1
	if( fScrollControl )
	{
		// Because we reverse the position on draw, we have to do some special trick here
		// to make sure the reversed position stays the same
		fScrollPos = (int)fScrollControl->GetMax() - (int)fScrollControl->GetCurrValue();
		if( fScrollPos >= fWrapStartIdxs.GetCount() )
			fScrollPos = fWrapStartIdxs.GetCount() - 1;

		if( i < 0 )
			// Smaller than the viewing area, so we don't scroll at all
			fScrollControl->SetRange( 0.f, 0.f );
		else
			fScrollControl->SetRange( 0.f, (hsScalar)( i + 1 ) );

		fScrollControl->SetCurrValue( (hsScalar)( (int)fScrollControl->GetMax() - fScrollPos ) );
	}
}

void pfGUIListBoxMod::PurgeDynaTextMapImage()
{
	if ( fDynTextMap != nil )
		fDynTextMap->PurgeImage();
}

//// Read/Write //////////////////////////////////////////////////////////////

void	pfGUIListBoxMod::Read( hsStream *s, hsResMgr *mgr )
{
	pfGUIControlMod::Read(s, mgr);

	fScrollControl = nil;
	if( s->ReadBool() )
	{
		fScrollProc = TRACKED_NEW pfScrollProc( this );
		fScrollProc->IncRef();
		mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, -1, kRefScrollCtrl ), plRefFlags::kActiveRef );
	}

	if( HasFlag( kDisableSelection ) )
		ClearFlag( kWantsInterest );

}

void	pfGUIListBoxMod::Write( hsStream *s, hsResMgr *mgr )
{
	pfGUIControlMod::Write( s, mgr );

	if( fScrollControl != nil )
	{
		s->WriteBool( true );
		mgr->WriteKey( s, fScrollControl->GetKey() );
	}
	else
		s->WriteBool( false );
}

//// FilterMousePosition /////////////////////////////////////////////////////
//	Tests the mouse point and decides whether we still want to accept input
//	based on the position. This allows us to act etheral (i.e. pass mouse
//	messages through) when the mouse is over an empty portion of the list.

hsBool	pfGUIListBoxMod::FilterMousePosition( hsPoint3 &mousePt )
{
	if( !HasFlag( kAllowMousePassThrough ) )
		return true;

	Int32 hover = fCurrClick = IGetItemFromPoint( mousePt );
	if( hover == -1 )
		return false;

	return true;
}

//// HandleMouseDown /////////////////////////////////////////////////////////
//	What we do: normal click deselects all and selects the item clicked on
//	(if any). Shift-click and ctrl-click avoids the deselect and toggles
//	the item clicked on.

void	pfGUIListBoxMod::HandleMouseDown( hsPoint3 &mousePt, UInt8 modifiers )
{
	Int32	i;

	int lastSelection = -1;
	if (HasFlag(kForbidNoSelection))
	{
		// grab the last item we had selected just in case they clicked outside the list of selectable objects
		for (i=0; i<fElements.GetCount(); i++)
			if (fElements[i]->IsSelected())
			{
				lastSelection = i;
				break;
			}
	}
	
	if( HasFlag(kSingleSelect) || ( !( modifiers & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) && !HasFlag( kHandsOffMultiSelect ) ) )
	{
		// Deselect everyone!
		for( i = 0; i < fElements.GetCount(); i++ )
			fElements[ i ]->SetSelected( false );
		fSingleSelElement = -1;
	}
	else if( modifiers & pfGameGUIMgr::kShiftDown )
	{
		IFindSelectionRange( &fMinSel, &fMaxSel );
	}

	fCurrHover = fCurrClick = IGetItemFromPoint( mousePt );
	if( fCurrClick != -1 )
	{
		if( ( ( modifiers & pfGameGUIMgr::kShiftDown ) && !HasFlag( kSingleSelect ) ) )
		{
			// Select our new range, deselect everything outside
			if( fCurrClick <= fMaxSel )
			{
				ISelectRange( 0, (Int8)(fCurrClick - 1), false );
				ISelectRange( (Int8)fCurrClick, (Int8)fMaxSel, true );
				ISelectRange( (Int8)(fMaxSel + 1), -1, false );
			}
			else if( fCurrClick >= fMinSel )
			{
				ISelectRange( 0, (Int8)(fMinSel - 1), false );
				ISelectRange( (Int8)fMinSel, (Int8)fCurrClick, true );
				ISelectRange( (Int8)(fCurrClick + 1), -1, false );
			}
			fElements[ fCurrClick ]->SetSelected( true );
		}
		else
		{
			fElements[ fCurrClick ]->SetSelected( true );
			fSingleSelElement = fCurrClick;
		}
	}
	else
	{
		if (HasFlag(kForbidNoSelection) && (lastSelection != -1))
		{
			fElements[ lastSelection ]->SetSelected(true);
			fSingleSelElement = lastSelection;
		}
	}

	// We want drag/up events to be processed with the modifiers we had on
	// mouse down, not the current ones. So store 'em for reference
	fModsAtDragTime = modifiers;
	fClicking = true;
	IUpdate();
}

//// HandleMouseUp ///////////////////////////////////////////////////////////

void	pfGUIListBoxMod::HandleMouseUp( hsPoint3 &mousePt, UInt8 modifiers )
{
	fClicking = false;
	if( !( fModsAtDragTime & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) && !HasFlag( kHandsOffMultiSelect ) )
	{
		// No modifiers--simply select whatever item we're on
		Int32	item = IGetItemFromPoint( mousePt );
		if( item != fCurrClick )
		{
			if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() )
				fElements[ fCurrClick ]->SetSelected( false );
			fCurrHover = fCurrClick = item;
			if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() )
				fElements[ fCurrClick ]->SetSelected( true );
			else if (HasFlag(kForbidNoSelection) && fCurrClick == -1)
			{
				fElements[fSingleSelElement]->SetSelected(true);
				fCurrClick = fSingleSelElement;
			}
			fCheckScroll = true;
			fSingleSelElement = fCurrClick;
			IUpdate();
		}
		else
		{
			// We didn't change selection, so go ahead and pass the click on to
			// the item, in case it wants to do something
			if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() )
			{
				hsPoint3 localPt = mousePt;
				IScreenToLocalPt( localPt );

				UInt16 lX = (UInt16)(( localPt.fX * ( fDynTextMap->GetVisibleWidth() - 1 ) ) - fElementBounds[ fCurrClick ].fLeft);
				UInt16 lY = (UInt16)(( localPt.fY * ( fDynTextMap->GetVisibleHeight() - 1 ) )- fElementBounds[ fCurrClick ].fTop);

				if( fElements[ fCurrClick ]->MouseClicked( lX, lY ) )
				{
					ICalcWrapStarts();
					ICalcScrollRange();
					IUpdate();
				}
			}
		}
	}

	DoSomething();	// Change in selection triggers our DoSomething() call
}

//// HandleMouseHover ////////////////////////////////////////////////////////
//	Just updates our currHover item so we can update the mouse appropriately

void	pfGUIListBoxMod::HandleMouseHover( hsPoint3 &mousePt, UInt8 modifiers )
{
	fCurrHover = IGetItemFromPoint( mousePt );
}

//// HandleMouseDrag /////////////////////////////////////////////////////////

void	pfGUIListBoxMod::HandleMouseDrag( hsPoint3 &mousePt, UInt8 modifiers )
{
	int		i;


	if( ( fModsAtDragTime & pfGameGUIMgr::kShiftDown && !HasFlag( kSingleSelect ) ) )
	{
		// Select our new range, deselect everything outside
		if( fCurrClick <= fMaxSel )
		{
			ISelectRange( 0, (Int8)(fCurrClick - 1), false );
			ISelectRange( (Int8)fCurrClick, (Int8)fMaxSel, true );
			ISelectRange( (Int8)(fMaxSel + 1), -1, false );
		}
		else if( fCurrClick >= fMinSel )
		{
			ISelectRange( 0, (Int8)(fMinSel - 1), false );
			ISelectRange( (Int8)fMinSel, (Int8)fCurrClick, true );
			ISelectRange( (Int8)(fCurrClick + 1), -1, false );
		}
		IUpdate();
	}
	else if( !( fModsAtDragTime & ( pfGameGUIMgr::kShiftDown | pfGameGUIMgr::kCtrlDown ) ) )
	{
		// No modifiers--simply select whatever item we're on
		if( HasFlag( kDragAndDropCapable ) )
		{
			// If we're drag and drop capable, then a mouse drag function results in
			// the dialog entering Drag Mode(tm). Basically, we tell our parent dialog
			// which items to drag and it'll run with the rest
			fDialog->ClearDragList();
			for( i = 0; i < fElements.GetCount(); i++ )
			{
				if( fElements[ i ]->IsSelected() )
					fDialog->AddToDragList( fElements[ i ] );
			}
			fDialog->EnterDragMode( this );
		}
		else
		{
			Int32	item = IGetItemFromPoint( mousePt );
			if( item != fCurrClick )
			{
				if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() )
					fElements[ fCurrClick ]->SetSelected( false );
				fCurrHover = fCurrClick = item;
				if( fCurrClick >= 0 && fCurrClick < fElements.GetCount() )
					fElements[ fCurrClick ]->SetSelected( true );
				fCheckScroll = true;
				fSingleSelElement = fCurrClick;
				IUpdate();
			}
		}
	}
}

//// HandleMouseDblClick /////////////////////////////////////////////////////

void	pfGUIListBoxMod::HandleMouseDblClick( hsPoint3 &mousePt, UInt8 modifiers )
{
	if( !HasFlag( kGrowLeavesAndProcessOxygen ) )
		return;		// We only care about double clicks if we make oxygen

	Int32 item = IGetItemFromPoint( mousePt );
	if( item != -1 )
	{
		if( fElements[ item ]->GetType() == pfGUIListElement::kTreeRoot )
		{
			pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ item ];

			root->ShowChildren( !root->IsShowingChildren() );
			if( !fLocked )
			{
				ICalcWrapStarts();
				ICalcScrollRange();
				IUpdate();
			}
		}
	}
}

//// IGetItemFromPoint ///////////////////////////////////////////////////////

Int32	pfGUIListBoxMod::IGetItemFromPoint( hsPoint3 &mousePt )
{
	if( !fBounds.IsInside( &mousePt ) )
		return -1;

	hsPoint3	localPt = mousePt; // despite getting a ref to the point (why?) we do NOT want to modify it
	IScreenToLocalPt( localPt );
	UInt32		i;
	Int16		clickItem, clickY = (Int16)( localPt.fY * ( fDynTextMap->GetVisibleHeight() - 1 ) );
	Int16		clickX = (Int16)( localPt.fX * ( fDynTextMap->GetVisibleWidth() - 1 ) );

	// We have a nice array that has the starting (top) Y's of each visible element. So we just
	// check against that.
	UInt32 startAt = 0;
	// make sure that we have a valid fScrollPos
	if ( fScrollPos != -1 )
	{
		// make sure that there is an Idx at fScrollPos
		if ( fWrapStartIdxs.GetCount() > fScrollPos )
		{
			startAt = fWrapStartIdxs[ fScrollPos ];
			clickItem = -1;
		}
	}
	for( i = startAt; i < fElements.GetCount(); i++ )
	{
		if( i<fElementBounds.GetCount() && fElementBounds[ i ].Contains( clickX, clickY ) )
		{
			clickItem = (Int16)i;
			break;
		}
	}

	if( clickItem > fElements.GetCount() - 1 || clickItem < 0 )
		clickItem = -1;

	return clickItem;
}

//// IFindSelectionRange /////////////////////////////////////////////////////
//	Find the min and max item that's selected. Returns -1 for both if nobody
//	is selected

void	pfGUIListBoxMod::IFindSelectionRange( Int32 *min, Int32 *max )
{
	Int32		i;


	*min = *max = -1;

	for( i = 0; i < fElements.GetCount(); i++ )
	{
		if( fElements[ i ]->IsSelected() )
		{
			*min = i;
			break;
		}
	}

	for( i = fElements.GetCount() - 1; i >= 0; i-- )
	{
		if( fElements[ i ]->IsSelected() )
		{
			*max = i;
			break;
		}
	}
}

//// ISelectRange ////////////////////////////////////////////////////////////

void	pfGUIListBoxMod::ISelectRange( Int8 min, Int8 max, hsBool select )
{
	Int16	i;


	if( max == -1 )
		max = fElements.GetCount() - 1;

	for( i = min; i <= max; i++ )
		fElements[ i ]->SetSelected( select );
}

//// SetSelection ////////////////////////////////////////////////////////////

void	pfGUIListBoxMod::SetSelection( Int32 item )
{
	if( HasFlag( kSingleSelect ) )
	{
		// Easy, only one item selected
		if( fSingleSelElement != -1 )
			fElements[ fSingleSelElement ]->SetSelected( false );

		fSingleSelElement = item;
	}
	else
		ISelectRange( 0, -1, false );

	if( item != -1 && item < fElements.GetCount() )
		fElements[ item ]->SetSelected( true );

	fCheckScroll = true;
	IUpdate();
}

void	pfGUIListBoxMod::RemoveSelection( Int32 item )
{
	// make sure the item is valid
	if ( item != -1 && item < fElements.GetCount() )
	{
		// if single select and its what is selected
		if ( HasFlag( kSingleSelect) && fSingleSelElement == item )
		{
			// then remove the selection and make nothing selected
			fElements[ fSingleSelElement ]->SetSelected( false );
			fSingleSelElement = item;
		}
		// else if multiple selection
		else
			// just remove the selection
			fElements[ item ]->SetSelected( false );
	}
	fCheckScroll = true;
	IUpdate();
}

void	pfGUIListBoxMod::AddSelection( Int32 item )
{
	// make sure the item is valid (can't add a non-selection!)
	if ( item != -1 && item < fElements.GetCount() )
	{
		// if single select then just like SetSelection
		if ( HasFlag( kSingleSelect) )
		{
			SetSelection(item);
		}
		// else if multiple selection
		else
			// just set its selection
			fElements[ item ]->SetSelected( true );
	}
	fCheckScroll = true;
	IUpdate();
}

//// HandleKeyPress //////////////////////////////////////////////////////////

hsBool	pfGUIListBoxMod::HandleKeyPress( char key, UInt8 modifiers )
{
	return false;
}

hsBool	pfGUIListBoxMod::HandleKeyEvent( pfGameGUIMgr::EventType event, plKeyDef key, UInt8 modifiers )
{
	if( key == KEY_CAPSLOCK )
		return false;

	if( HasFlag( kDisableKeyActions ) )
		return false;

	if( event == pfGameGUIMgr::kKeyDown || event == pfGameGUIMgr::kKeyRepeat )
	{
		// Use arrow keys to do our dirty work
		if( key == KEY_UP )
		{
			if( fCurrClick == -1 && fElements.GetCount() > 0 )
				fCurrClick = 0;
			else while( fCurrClick > 0 )
			{
				fCurrClick--;
				if( !HasFlag( kGrowLeavesAndProcessOxygen ) || !fElements[ fCurrClick ]->IsCollapsed() )
					break;
			}
		}
		else if( key == KEY_DOWN )
		{
			if( fCurrClick == -1 && fElements.GetCount() > 0 )
				fCurrClick = fElements.GetCount() - 1;
			else while( fCurrClick < fElements.GetCount() - 1 )
			{
				fCurrClick++;
				if( !HasFlag( kGrowLeavesAndProcessOxygen ) || !fElements[ fCurrClick ]->IsCollapsed() )
					break;
			}
		}
		else if( key == KEY_HOME )
		{
			if( fElements.GetCount() > 0 )
				fCurrClick = 0;			
		}
		else if( key == KEY_END )
		{
			if( fElements.GetCount() > 0 )
				fCurrClick = fElements.GetCount() - 1;			
		}
		else if( key == KEY_ENTER && HasFlag( kGrowLeavesAndProcessOxygen ) )
		{
			if( fCurrClick != -1 && fElements[ fCurrClick ]->GetType() == pfGUIListElement::kTreeRoot )
			{
				pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ fCurrClick ];
				root->ShowChildren( !root->IsShowingChildren() );

				if( !fLocked )
				{
					ICalcWrapStarts();
					ICalcScrollRange();
				}
			}
		}
		else
		{
			return false;
		}

		ISelectRange( 0, -1, false );
		if( fCurrClick != -1 )
			fElements[ fCurrClick ]->SetSelected( true );
		fSingleSelElement = fCurrClick;

		DoSomething();	// Change in selection triggers our DoSomething() call

		fCheckScroll = true;
		IUpdate();
		return true;
	}

	return false;
}

//// ScrollToEnd /////////////////////////////////////////////////////////////

void	pfGUIListBoxMod::ScrollToEnd( void )
{
	if( fScrollControl != nil )
	{
		fScrollPos = (int)fScrollControl->GetMin();
		fScrollControl->SetCurrValue( fScrollControl->GetMax() );
	}
	IUpdate();
}

//// ScrollToBegin ///////////////////////////////////////////////////////////

void	pfGUIListBoxMod::ScrollToBegin( void )
{
	if( fScrollControl != nil )
	{
		fScrollPos = (int)fScrollControl->GetMax();
		fScrollControl->SetCurrValue( fScrollControl->GetMin() );
	}
	IUpdate();
}

void		pfGUIListBoxMod::SetColorScheme( pfGUIColorScheme *newScheme )
{
	UInt16	i;

	pfGUIControlMod::SetColorScheme( newScheme );

	for( i = 0; i < fElements.GetCount(); i++ )
	{
		fElements[ i ]->SetColorScheme( newScheme );
		fElements[ i ]->SetSkin( fSkin );
	}
}

void pfGUIListBoxMod::SetScrollPos( Int32 pos )
{
	if ( pos >= (int)fScrollControl->GetMin() && pos <= (int)fScrollControl->GetMax() )
	{
		fScrollControl->SetCurrValue( (hsScalar)pos );
		fScrollPos = (int)fScrollControl->GetMax() - pos;
	}
	IUpdate();
}

Int32 pfGUIListBoxMod::GetScrollPos( void )
{
	return (int)fScrollControl->GetCurrValue();
}

Int32 pfGUIListBoxMod::GetScrollRange( void )
{
	return (int)fScrollControl->GetMax() - (int)fScrollControl->GetMin();
}


//////////////////////////////////////////////////////////////////////////////
//// Element Manipulation ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

UInt16	pfGUIListBoxMod::AddElement( pfGUIListElement *el )
{
	UInt16	idx = fElements.GetCount();
	fElements.Append( el );
	el->SetColorScheme( GetColorScheme() );
	el->SetSkin( fSkin );
	if( !fLocked )
	{
		ICalcWrapStarts();
		ICalcScrollRange();
		IUpdate();
		HandleExtendedEvent( pfGUIListBoxMod::kItemAdded );
	}
	return idx;
}

void	pfGUIListBoxMod::RemoveElement( UInt16 index )
{
	// Make sure no other elements care about this one
	UInt16 i, j;
	for( i = 0; i < fElements.GetCount(); i++ )
	{
		if( fElements[ i ]->GetType() == pfGUIListElement::kTreeRoot )
		{
			pfGUIListTreeRoot *root = (pfGUIListTreeRoot *)fElements[ i ];
			for( j = 0; j < root->GetNumChildren(); )
			{
				if( root->GetChild( j ) == fElements[ index ] )
					root->RemoveChild( j );
				else
					j++;
			}
		}
	}

	delete fElements[ index ];
	fElements.Remove( index );

	if( index == fSingleSelElement )
		fSingleSelElement = -1;
	else if( index < fSingleSelElement )
		fSingleSelElement--;
	fCurrHover = fCurrClick = -1;

	if( !fLocked )
	{
		ICalcWrapStarts();
		ICalcScrollRange();
		IUpdate();
		HandleExtendedEvent( pfGUIListBoxMod::kItemRemoved );
	}
}

Int16	pfGUIListBoxMod::FindElement( pfGUIListElement *toCompareTo )
{
	int		i;


	for( i = 0; i < fElements.GetCount(); i++ )
	{
		if( fElements[ i ]->CompareTo( toCompareTo ) == 0 )
			return i;
	}

	return (Int16)-1;
}

void	pfGUIListBoxMod::ClearAllElements( void )
{
	int		i;


	for( i = 0; i < fElements.GetCount(); i++ )
		delete fElements[ i ];
	fElements.Reset();
	fSingleSelElement = -1;

	if( !fLocked )
	{
		ICalcWrapStarts();
		ICalcScrollRange();
		IUpdate();
	}

	HandleExtendedEvent( pfGUIListBoxMod::kListCleared );
}

UInt16	pfGUIListBoxMod::AddString( const char *string )
{
	return AddElement( TRACKED_NEW pfGUIListText( string ) );
}

UInt16	pfGUIListBoxMod::AddString( const wchar_t *string )
{
	return AddElement( TRACKED_NEW pfGUIListText( string ) );
}

Int16	pfGUIListBoxMod::FindString( const char *toCompareTo )
{
	pfGUIListText	text( toCompareTo );
	return FindElement( &text );
}

Int16	pfGUIListBoxMod::FindString( const wchar_t *toCompareTo )
{
	pfGUIListText	text( toCompareTo );
	return FindElement( &text );
}

UInt16	pfGUIListBoxMod::GetNumElements( void )
{
	return fElements.GetCount();
}

pfGUIListElement	*pfGUIListBoxMod::GetElement( UInt16 idx )
{
	return fElements[ idx ];
}

void	pfGUIListBoxMod::LockList( void )
{
	fLocked = true;
}

void	pfGUIListBoxMod::UnlockList( void )
{
	fLocked = false;
	ICalcWrapStarts();
	ICalcScrollRange();
	IUpdate();
}

//// IGetDesiredCursor ///////////////////////////////////////////////////////

UInt32		pfGUIListBoxMod::IGetDesiredCursor( void ) const
{
	if( fCurrHover == -1 )
		return plInputInterface::kNullCursor;

	if( fClicking )
		return plInputInterface::kCursorClicked;

	return plInputInterface::kCursorPoised;
}