/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 )
{
	pfGUIColorScheme *scheme = GetColorScheme();
	fDynTextMap->SetFont( scheme->fFontFace, scheme->fFontSize, scheme->fFontFlags, 
							!HasFlag( kXparentBgnd ));

	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;
}