////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//////////////////////////////////////////////////////////////////////////////
//
// ListBox.CPP
// 
// History:
//		01/13/97 JMI	Started.
//
//		01/15/97	JMI	SetProp() now takes the Key parm first and the Item parm
//							second.  Changed this module to reflect that.
//
//		01/15/97	JMI	Added overrides of base class's Save/LoadChildren() to
//							implement special cases for m_sbVert, Horz, & 
//							m_lcContents.  List items saved and then loaded will not
//							be recognized as list items by this class.  This should
//							eventually be supported so one can save a list of items.
//
//		01/15/97	JMI	Since loading of list items does not work properly, any
//							list items in m_lcContents are destroyed immediately
//							after they are loaded.
//
//		01/20/97	JMI	UpdateScrollBarVisibility() was not working correctly for
//							Hidden.  Fixed.
//
//		01/21/97	JMI	Added ReadMembers() and WriteMembers() overloads to read
//							and write members of this class.  Note that they call the
//							base class version to read/write base class members.
//							Support exists for versions 0 and 1.  Version 0 did not
//							contain other than RGuiItem members.
//
//		01/21/97	JMI	RemoveItem() was not considering the possibly that the
//							item being removed might be the selected item.
//							No longer sizes an encapsulator/string item to 
//							m_sLargestWidth until the AdjustContents() call.
//
//		01/21/97	JMI	Changed m_frmContents (RFrame) to m_lcContents
//							(RListContents).
//
//		01/23/97	JMI	EnsureVisible() had an error that made it think that 
//							some items that were list items were not.
//
//		02/05/97	JMI	Changed position of default: case in ReadMembers().
//							And made CreateStringItem() use m_typeEncapsulator as
//							the type of the created encapsulator.
//							Note that CreateEncapsulator() still only uses RTxts.
//
//		02/25/97	JMI	SaveChildren() now goes through the children in reverse
//							order so they, on load, get added back to their parent in
//							the order they were originally added to this parent.
//
//		03/28/97	JMI	Now AdjustContents() adjusts vertical scroll bar incs
//							so that the buttons will advance one item and the tray
//							will scroll visible items - 1.
//
//		04/10/97	JMI	Now uses m_sFontCellHeight instead of GetPos() to get
//							cell height.
//
//		05/25/97	JMI	Added GetFirst(), GetNext(), and GetPrev().
//
//		06/15/97	JMI	List items are now at least as wide as the viewable area.
//
//		08/11/97	JMI	Seemed like there was a potential for infinite loop in
//							GetNext() and GetPrev().
//							Also, GetFirst() returned the first item in the list
//							w/o checking if it was an encapsulator.
//
//		09/01/97	JMI	Added MakeEncapsulator().
//
//		09/07/97	JMI	Now EnsureVisible() accepts an optional position 
//							preference so you can bias the location of your item.
//
//		09/24/97	JMI	Calls to InsertAfter() and InsertBefore() had their
//							parameters backward.
//
//////////////////////////////////////////////////////////////////////////////
//
// This a GUI item that is based on the basic RGuiItem. 
// This overrides HotCall() to get information about where a click in its RHot
// occurred.
// This overrides Compose() to create the scrollable area and compose 
// the scrollbars.
//
// Enhancements/Uses:
//		Not yet known.
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Headers.
//////////////////////////////////////////////////////////////////////////////
#include "Blue.h"

#ifdef PATHS_IN_INCLUDES
	#include "ORANGE/GUI/ListBox.h"
	#include "ORANGE/GUI/txt.h"
#else
	#include "listbox.h"
	#include "txt.h"
#endif // PATHS_IN_INCLUDES

//////////////////////////////////////////////////////////////////////////////
// Module specific macros.
//////////////////////////////////////////////////////////////////////////////

// Sets val to def if val is -1.
#define DEF(val, def)	((val == -1) ? def : val)

// Default width for vertical scrollbar and height for horizontal scrollbar.
#define DEF_SCROLL_THICKNESS	13

// Space between items.
#define ITEM_SPACING				m_sBorderThickness

// Scrollbar priority is very high so it will be above other siblings.
#define SCROLLBAR_PRIORITY		((short)0x8001)

// Frame should be lower priority so that scrollbars are 'on top' of it.
#define FRAME_PRIORITY			((short)0x7FFF)

//////////////////////////////////////////////////////////////////////////////
// Module specific typedefs.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) variables.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Construction/Destruction.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Default constructor.
//
//////////////////////////////////////////////////////////////////////////////
RListBox::RListBox()
	{
	// Override defaults for this.
	m_type						= ListBox;	// Indicates type of GUI item.
	m_sInvertedBorder			= TRUE;		// Want sunken look.
	
	// Override defaults for children.
	m_sbVert.m_im.m_sWidth		= DEF_SCROLL_THICKNESS;
	m_sbVert.m_oOrientation		= RScrollBar::Vertical;
	m_sbVert.m_upcUser			= ScrollCall;
	m_sbVert.m_ulUserInstance	= (U64)this;

	// Priority for scrollbars should be higher than any other sibling.
	m_sbVert.m_hot.SetPriority(SCROLLBAR_PRIORITY);

	m_sbHorz.m_im.m_sHeight		= DEF_SCROLL_THICKNESS;
	m_sbHorz.m_oOrientation		= RScrollBar::Horizontal;
	m_sbHorz.m_upcUser			= ScrollCall;
	m_sbHorz.m_ulUserInstance	= (U64)this;

	// Priority for scrollbars should be higher than any other sibling.
	m_sbHorz.m_hot.SetPriority(SCROLLBAR_PRIORITY);

	// Priority for frame is low.
	m_lcContents.m_hot.SetPriority(FRAME_PRIORITY);

	// Set defaults for direct members.
	m_sbvVert					= ShownAsNeeded;
	m_sbvHorz					= ShownAsNeeded;
									
	m_sLargestWidth			= 0;
									
	m_pguiSel					= NULL;
	
	m_typeEncapsulator		= Txt;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor.
//
//////////////////////////////////////////////////////////////////////////////
RListBox::~RListBox()
	{
	// Remove all items and destroy their encapsulators.
	RemoveAll();
	}

////////////////////////////////////////////////////////////////////////
// Methods.
////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Creates a displayable Listbox item.
//
//////////////////////////////////////////////////////////////////////////////
int16_t RListBox::Create(			// Returns 0 on success.
	int16_t sX,						// X position relative to "parent" item.
	int16_t sY,						// Y position relative to "parent" item.
	int16_t sW,						// Width.
	int16_t sH,						// Height.
	int16_t sDepth)					// Color depth.
	{
	int16_t	sRes	= 0;	// Assume success.

	Destroy();

	m_sX	= sX;
	m_sY	= sY;

	if (m_im.CreateImage(sW, sH, RImage::BMP8, 0, sDepth) == 0)
		{
		// Done.
		
		// If there's an error after calling CreateImage, perhaps
		// we should destroy the RImage data here.
		if (sRes != 0)
			{
			Destroy();
			}
		}
	else
		{
		TRACE("Create(): RImage::CreateImage() failed.\n");
		sRes = -1;
		}
	
	// Get client area.
	int16_t	sClientX, sClientY, sClientW, sClientH;
	GetClient(&sClientX, &sClientY, &sClientW, &sClientH);

	// Create child items:

	CopyParms(&m_lcContents);

	m_lcContents.m_sBorderThickness	= 0;

	// Frame is created in AdjustContents() call.
	m_lcContents.SetParent(this);

	CopyParms(&m_sbVert);

	if (m_sbVert.Create(
		sClientX + sClientW - m_sbVert.m_im.m_sWidth,
		sClientY,
		m_sbVert.m_im.m_sWidth,
		sClientH,
		sDepth) == 0)
		{
		m_sbVert.SetParent(this);
		}
	else
		{
		TRACE("Create(): m_sbVert.Create() failed.\n");
		// Don't use this.
		m_sbVert.SetParent(NULL);
		}

	CopyParms(&m_sbHorz);

	if (m_sbHorz.Create(
		sClientX,
		sClientY + sClientH - m_sbHorz.m_im.m_sHeight,
		sClientW - m_sbVert.m_im.m_sWidth,
		m_sbHorz.m_im.m_sHeight,
		sDepth) == 0)
		{
		m_sbHorz.SetParent(this);
		}
	else
		{
		TRACE("Create(): m_sbHorz.Create() failed.\n");
		// Don't use this.
		m_sbHorz.SetParent(NULL);
		}
	
	// Compose this item.
	Compose();

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Compose item.
//
////////////////////////////////////////////////////////////////////////
void RListBox::Compose(			// Returns nothing.
	RImage* pim /*= NULL*/)	// Dest image, uses m_im if NULL.
	{
	if (pim == NULL)
		{
		pim	= &m_im;
		}

	// Call base (draws border and background).
	RGuiItem::Compose(pim);

	// Resizes all encapsulators, m_lcContents, and sets scroll ranges.
	AdjustContents();

	// No text.
	}

////////////////////////////////////////////////////////////////////////
//
// Add a string into the list box.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::AddString(		// Returns new GUI item on success.
	char*	pszString,						// String to add.
	RGuiItem* pguiAfter /*= NULL*/)	// Gui to add after or NULL to add at
												// end.
	{
	// Create a new string item . . .
	RGuiItem*	pguiRes	= CreateStringItem(pszString);
	if (pguiRes != NULL)
		{
		// Reposition item.
		AddAfter(pguiRes, pguiAfter);
		}
	else
		{
		TRACE("AddString(): CreateStringItem() failed.\n");
		}

	return pguiRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Insert a string into the list box.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::InsertString(	// Returns new GUI item on success.
	char*	pszString,						// String to insert.
	RGuiItem* pguiBefore /*= NULL*/)	// Gui to insert before or NULL to 
												// insert at beginning.
	{
	// Create a new string item . . .
	RGuiItem*	pguiRes	= CreateStringItem(pszString);
	if (pguiRes != NULL)
		{
		// Reposition item.
		InsertBefore(pguiRes, pguiBefore);
		}
	else
		{
		TRACE("InsertString(): CreateStringItem() failed.\n");
		}

	return pguiRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Add an item into the list box.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::AddItem(			// Returns new GUI item or pgui on
												// success.  Depends on sEncapsulate.
	RGuiItem* pgui,						// GUI item to insert.
	int16_t	sEncapsulate /*= FALSE*/,	// If TRUE, this item will be 
												// encapsulated in an RGuiItem that
												// will be returned on success.
												// If FALSE, this item will be a direct
												// child of the listbox and will be
												// returned on success.
	RGuiItem* pguiAfter /*= NULL*/)	// Gui to add after or NULL to add at
												// end.
	{
	RGuiItem*	pguiRes	= NULL;	// Assume nothing.

	// If encapsulation requested . . .
	if (sEncapsulate != FALSE)
		{
		// Create encapsulator item.
		pguiRes	= CreateEncapsulator(pgui);
		}
	else
		{
		// The item is the item to be placed in the listbox.
		pguiRes	= pgui;
		}

	// If we have the item to be added . . .
	if (pguiRes != NULL)
		{
		// Make child of list area.
		pguiRes->SetParent(&m_lcContents);
		// Reposition item.
		AddAfter(pguiRes, pguiAfter);
		}
	else
		{
		TRACE("AddItem(): CreateEncapsulator() failed.\n");
		}

	return pguiRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Insert an item into the list box.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::InsertItem(		// Returns new GUI item or pgui on
												// success.  Depends on sEncapsulate.
	RGuiItem* pgui,						// GUI item to insert.
	int16_t	sEncapsulate /*= FALSE*/,	// If TRUE, this item will be 
												// encapsulated in an RGuiItem that
												// will be returned on success.
												// If FALSE, this item will be a direct
												// child of the listbox and will be
												// returned on success.
	RGuiItem* pguiBefore /*= NULL*/)	// Gui to insert before or NULL to 
												// insert at beginning.
	{
	RGuiItem*	pguiRes	= NULL;	// Assume nothing.

	// If encapsulation requested . . .
	if (sEncapsulate != FALSE)
		{
		// Create encapsulator item.
		pguiRes	= CreateEncapsulator(pgui);
		}
	else
		{
		// The item is the item to be placed in the listbox.
		pguiRes	= pgui;
		}

	// If we have the item to be added . . .
	if (pguiRes != NULL)
		{
		// Make child of list area.
		pguiRes->SetParent(&m_lcContents);
		// Reposition item.
		InsertBefore(pguiRes, pguiBefore);
		}
	else
		{
		TRACE("AddItem(): CreateEncapsulator() failed.\n");
		}

	return pguiRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Remove an item from the list box and destroy encapsulator, if an
// encapsulator exists for that item.
//
////////////////////////////////////////////////////////////////////////
void RListBox::RemoveItem(			// Returns nothing.
	RGuiItem* pgui)					// Item to remove.  NOTE:  Any 
											// encapsulating GUI item will be
											// destroyed.
	{
	// Verify item is a list item and get encapsulator, if any:
	RGuiItem*	pguiParent	= pgui->GetParent();
	RGuiItem*	pguiRemove	= NULL;
	if (pguiParent != NULL)
		{
		// If parent is the list container . . .
		if (pguiParent == &m_lcContents)
			{
			// Set item to remove.
			pguiRemove	= pgui;
			}
		else
			{
			// If parent's parent is the list container . . .
			if (pguiParent->GetParent() == &m_lcContents)
				{
				// Free from encapsulator.
				pgui->SetParent(NULL);
				// Set item to remove.
				pguiRemove	= pguiParent;
				}
			}
		}

	// If there is an item to remove . . .
	if (pguiRemove != NULL)
		{
		// If this item is an encapsulator . . .
		if (IsEncapsulator(pguiRemove) != FALSE)
			{
			// If this item was the selected item . . .
			if (pguiRemove == m_pguiSel)
				{
				// Clear selection.
				SetSel(NULL);
				}

			// Remove encapsulator prop.
			pguiRemove->RemoveProp(ENCAPSULATOR_PROP_KEY);
			// Destroy item.
			delete pguiRemove;
			}
		else
			{
			// If dynamic . . .
			if (pguiRemove->IsDynamic() != FALSE)
				{
				delete pguiRemove;
				}
			}
		}
	else
		{
		TRACE("RemoveItem():  Specified item is not an item of this listbox.\n");
		}

	}

////////////////////////////////////////////////////////////////////////
//
// Remove all items and encapsulators in listbox.
// Calls RemoveItem() for each item.
//
////////////////////////////////////////////////////////////////////////
void RListBox::RemoveAll(void)	// Returns nothing.
	{
	RGuiItem*	pgui	= m_lcContents.m_listguiChildren.GetHead();
	while (pgui != NULL)
		{
		RemoveItem(pgui);

		pgui	= m_lcContents.m_listguiChildren.GetNext();
		}
	}

////////////////////////////////////////////////////////////////////////
//
// Ensure the specified item is within the client of the listbox.
//
////////////////////////////////////////////////////////////////////////
void RListBox::EnsureVisible(				// Returns nothing.
	RGuiItem* pgui,							// Item to ensure visibility of.
	Position	posPreference /*= Top*/)	// In:  Preferred vertical position.
	{
	int16_t	sX	= pgui->m_sX;
	int16_t	sY	= pgui->m_sY;

	// Take position of item to list contents level.
	RGuiItem*	pguiParent	= pgui->GetParent();
	while (pguiParent != NULL && pguiParent != &m_lcContents)
		{
		sX	+= pguiParent->m_sX;
		sY	+= pguiParent->m_sY;

		pguiParent	= pguiParent->GetParent();
		}

	// If we got to the list contents . . .
	if (pguiParent != NULL)
		{
		// Set new position for list contents such that item
		// is in upper left corner of client.
		int16_t	sClientX, sClientY, sClientH;
		GetClient(&sClientX, &sClientY, NULL, &sClientH);

		switch (posPreference)
			{
			case Top:
				m_lcContents.Move(-sX + sClientX, -sY + sClientY);
				break;
			case Middle:
				m_lcContents.Move(-sX + sClientX, -sY + sClientY + (sClientH - pgui->m_im.m_sHeight) / 2);
				break;
			case Bottom:
				m_lcContents.Move(-sX + sClientX, -sY + sClientY + sClientH - pgui->m_im.m_sHeight);
				break;
			}

		// Update the scrollbars.
		UpdateScrollBars();
		}
	else
		{
		TRACE("EnsureVisible(): Specified gui is not a member of this "
			"listbox.\n");
		}
	}

////////////////////////////////////////////////////////////////////////
//
// Set the selection to the specified GUI item.
//
////////////////////////////////////////////////////////////////////////
void RListBox::SetSel(				// Returns nothing.
	RGuiItem* pgui)					// Item to select or NULL for none.
	{
	// If new selection is different than current . . .
	if (pgui != m_pguiSel)
		{
		// Unselect item.
		SelectItem(m_pguiSel, FALSE);

		// Set new selection.
		m_pguiSel	= pgui;

		// Select item.
		SelectItem(m_pguiSel, TRUE);
		}
	}

////////////////////////////////////////////////////////////////////////
//
// Update scrollbar visibility based on current listbox contents and
// their visibility.
//
////////////////////////////////////////////////////////////////////////
void RListBox::UpdateScrollBarVisibility(void)	// Returns nothing.
	{
	// Get client.
	int16_t	sClientX, sClientY, sClientW, sClientH;
	GetClient(&sClientX, &sClientY, &sClientW, &sClientH);

	// If visibility is not prohibited . . .
	if (m_sbvVert != Hidden)
		{
		// If the vertical scrollbar should be visible . . .
		if (m_lcContents.m_im.m_sHeight > sClientH - m_sbHorz.m_im.m_sHeight || m_sbvVert == Shown)
			{
			m_sbVert.m_sVisible	= TRUE;
			}
		else
			{
			m_sbVert.m_sVisible	= FALSE;
			}
		}
	else
		{
		m_sbVert.m_sVisible	= FALSE;
		}

	// If visibility is not prohibited . . .
	if (m_sbvHorz != Hidden)
		{
		// If the horizontal scrollbar should be visible . . .
		if (m_lcContents.m_im.m_sWidth > sClientW - m_sbVert.m_im.m_sWidth || m_sbvHorz == Shown)
			{
			m_sbHorz.m_sVisible	= TRUE;
			}
		else
			{
			m_sbHorz.m_sVisible	= FALSE;
			}
		}
	else
		{
		m_sbHorz.m_sVisible	= FALSE;
		}

	// Activate scrollbars according to their visibility and current 
	// activation status of listbox.
	m_sbVert.SetActive(m_sbVert.m_sVisible && IsActivated());
	m_sbHorz.SetActive(m_sbHorz.m_sVisible && IsActivated());
	}

////////////////////////////////////////////////////////////////////////
//
// Resize encapsulators to fit the largest encapsulated item or string,
// reposition all items to appear in the correct order, resize scrollable
// area (m_lcContents) to fit all listbox items, and update scrollbar 
// ranges accordingly.
//
////////////////////////////////////////////////////////////////////////
void RListBox::AdjustContents(void)					// Returns nothing.
	{
	// Get client.
	int16_t	sClientX, sClientY, sClientW, sClientH;
	GetClient(&sClientX, &sClientY, &sClientW, &sClientH);

	// Minimum is viewable area (when vertical scrollbar shown).
	m_sLargestWidth		= sClientW - m_sbVert.m_im.m_sWidth;

	int32_t	lTotalHeight	= 0;	// Used for averaging heights.
	int32_t	lTotalItems		= 0;	// Used for averaging heights.

	// Get largest width.
	RGuiItem*	pguiItem	= m_lcContents.m_listguiChildren.GetHead();
	while (pguiItem != NULL)
		{
		// If width is larger than largest . . .
		if (pguiItem->m_im.m_sWidth > m_sLargestWidth)
			{
			m_sLargestWidth	= pguiItem->m_im.m_sWidth;
			}

		// Get next item.
		pguiItem	= m_lcContents.m_listguiChildren.GetNext();
		}

	// Go through children sizing encapsulators to m_sLargestWidth and
	// repositioning items as we go.
	int16_t	sY	= 0;
	pguiItem	= m_lcContents.m_listguiChildren.GetHead();
	while (pguiItem != NULL)
		{
		// Reposition.
		pguiItem->Move(pguiItem->m_sX, sY);

		// If this is an encapsulator . . .
		if (IsEncapsulator(pguiItem) != FALSE)
			{
			// If there's a change in width . . .
			if (pguiItem->m_im.m_sWidth != m_sLargestWidth)
				{
				// Recreate item . . .
				if (pguiItem->Create(
					pguiItem->m_sX,
					pguiItem->m_sY,
					m_sLargestWidth,
					pguiItem->m_im.m_sHeight,
					pguiItem->m_im.m_sDepth) == 0)
					{
					}
				else
					{
					TRACE("AdustContents(): pguiItem->Create() failed.\n");
					}
				}
			}

		// Adjust position for next item by this item's height and the
		// item spacing.
		sY	+= pguiItem->m_im.m_sHeight + ITEM_SPACING;

		// Keep total height so we can average it to determine scrollbar
		// increments.
		lTotalHeight	+= pguiItem->m_im.m_sHeight + ITEM_SPACING;
		lTotalItems++;

		// Get next item.
		pguiItem	= m_lcContents.m_listguiChildren.GetNext();
		}

	// New height and/or width for container . . .
	if (m_lcContents.Create(
		m_lcContents.m_sX,
		m_lcContents.m_sY,
		m_sLargestWidth,
		sY - ITEM_SPACING,
		m_lcContents.m_im.m_sDepth) == 0)
		{
		}
	else
		{
		TRACE("AdjustContents(): m_lcContents.Create() failed.\n");
		}

	// Update scrollbar visibility.
	UpdateScrollBarVisibility();

	// Set scroll ranges:

	// The scrollable range is the length of the frame minus the size of
	// the area that can be viewed.  This is subtracted from the client
	// position to so it is already mapped directly into 'this's 
	// coordinate system.

	// If the horizontal scrollbar is visible . . .
	int16_t	sScrollBarH	= 0;
	if (m_sbHorz.m_sVisible != FALSE)
		{
		// Compensation:
		sScrollBarH	= m_sbHorz.m_im.m_sHeight;
		}

	// If viewable area is smaller than list area . . .
	if (m_lcContents.m_im.m_sHeight > sClientH - sScrollBarH)
		{
		m_sbVert.SetRange(
			-sClientY,
			(m_lcContents.m_im.m_sHeight - (sClientH - sScrollBarH)) - sClientY);
		}
	else
		{
		m_sbVert.SetRange(
			-sClientY,
			-sClientY);
		}
	
	// If the vertical scrollbar is visible . . .
	int16_t	sScrollBarW	= 0;
	if (m_sbVert.m_sVisible != FALSE)
		{
		// Compensation:
		sScrollBarW	= m_sbVert.m_im.m_sWidth;
		}

	// If viewable area is smaller than list area . . .
	if (m_lcContents.m_im.m_sWidth > sClientW - sScrollBarW)
		{
		m_sbHorz.SetRange(
			-sClientX,
			(m_lcContents.m_im.m_sWidth - (sClientW - sScrollBarW)) - sClientX); 
		}
	else
		{
		m_sbHorz.SetRange(
			-sClientX,
			-sClientX);
		}

	// Calculate average height of item.
	int32_t	lAvgHeight	= 0;
	if (lTotalItems > 0)
		{
		lAvgHeight	= (int16_t)(lTotalHeight / lTotalItems);
		}
	
	// Update vertical scroll bar increments:
	
	// Clicking buttons should scroll one item at a time.
	m_sbVert.m_lButtonIncDec	= lAvgHeight;
	// Clicking tray should scroll visible items - 1 at a time.
	m_sbVert.m_lTrayIncDec		= (sClientH - sScrollBarH) - lAvgHeight;

	// Safety . . .
	if (m_sbVert.m_lTrayIncDec <= 0)
		{
		m_sbVert.m_lTrayIncDec	= lAvgHeight;
		}

	// Update scroll positions.
	UpdateScrollBars();
	}

////////////////////////////////////////////////////////////////////////
//
// Update scrollbars' positions.  This function is called by 
// AdjustContents() and EnsureVisible(), so there is no need to call it
// after calling one of those functions.
//
////////////////////////////////////////////////////////////////////////
void RListBox::UpdateScrollBars(void)
	{
	m_sbVert.SetPos(-m_lcContents.m_sY);
	m_sbHorz.SetPos(-m_lcContents.m_sX);
	}

////////////////////////////////////////////////////////////////////////
// Querries.
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
// Get the first child item.
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::GetFirst(void)	// Returns the first child item or
												// NULL.
	{
	// Find first encapsulator.
	RGuiItem*	pguiFirst	= m_lcContents.m_listguiChildren.GetHead();
	while (pguiFirst != NULL)
		{
		if (IsEncapsulator(pguiFirst) != FALSE)
			{
			break;
			}

		pguiFirst	= m_lcContents.m_listguiChildren.GetNext();
		}

	return pguiFirst;
	}

////////////////////////////////////////////////////////////////////////
// Get the next child item after the specified item.
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::GetNext(		// Returns the next child item.
	RGuiItem*	pgui)					// In:  Child item that precedes the next.
	{
	RGuiItem*	pguiNext	= m_lcContents.m_listguiChildren.GetNext(pgui);
	while (pguiNext != NULL)
		{
		if (IsEncapsulator(pguiNext) != FALSE)
			{
			break;
			}

		pguiNext	= m_lcContents.m_listguiChildren.GetNext();
		}

	return pguiNext;
	}

////////////////////////////////////////////////////////////////////////
// Get the child item before the specified item.
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::GetPrev(		// Returns the previous child item or NULL.
	RGuiItem*	pgui)					// In:  Child item that follows the prev.
	{
	RGuiItem*	pguiPrev	= m_lcContents.m_listguiChildren.GetPrev(pgui);
	while (pguiPrev != NULL)
		{
		if (IsEncapsulator(pguiPrev) != FALSE)
			{
			break;
			}

		pguiPrev	= m_lcContents.m_listguiChildren.GetPrev();
		}

	return pguiPrev;
	}

////////////////////////////////////////////////////////////////////////
// Internals.
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
//
// Creates an item appropriate for the specified string and makes it
// a child of this listbox.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::CreateStringItem(	// Returns new item on success;
													// NULL, otherwise.
	char* pszString)							// Text for new item.
	{
	RGuiItem*	pgui		= NULL;	// Assume nothing.
	int16_t	sError	= 0;

	// We'll need a font and a print for this.
	ASSERT(m_pprint != NULL);
	ASSERT(m_pprint->GetFont() != NULL);

	// Allocate a new item . . .
	pgui	= CreateGuiItem(m_typeEncapsulator);
	if (pgui != NULL)
		{
		// Copy parms to new item.
		CopyParms(pgui);

		// Set callback.
		pgui->m_bcUser				= PressedCall;
		pgui->m_ulUserInstance	= (U64)this;

		// Get thickness of border.
		int16_t sBorder	= pgui->GetTopLeftBorderThickness()
			+ pgui->GetBottomRightBorderThickness();

		// Get the width of the desired string.
		int16_t	sWidth	= m_pprint->GetWidth(pszString);
		int16_t	sHeight	= m_sFontCellHeight;

		// Adjust by border thickness.
		sWidth	+= sBorder;
		sHeight	+= sBorder;

		// Set text for item before Create() (Create() calls Compose()).
		pgui->SetText(pszString);

		// Create item . . .
		if (pgui->Create(0, 0, sWidth, sHeight, m_im.m_sDepth) == 0)
			{
			// Make child of list area.
			pgui->SetParent(&m_lcContents);
			// Remember to delete this item.
			MakeEncapsulator(pgui);
			// Item position is not set until the next AdjustContents() call.
			}
		else
			{
			TRACE("CreateStringItem(): ptxt->Create() failed.\n");
			sError	= 2;
			}

		// If any errors occurred after allocation . . .
		if (sError != 0)
			{
			delete pgui;
			pgui	= NULL;
			}
		}
	else
		{
		TRACE("CreateStringItem(): Failed to allocate encapsulator of type %s.\n",
			ms_apszTypes[m_typeEncapsulator]);
		sError	= 1;
		}

	return pgui;
	}

////////////////////////////////////////////////////////////////////////
//
// Creates an encapsulator object for the specified GUI.
//
////////////////////////////////////////////////////////////////////////
RGuiItem* RListBox::CreateEncapsulator(	// Returns new item on success; NULL,
														// otherwise.
	RGuiItem*	pgui)								// Item to encapsulate.
	{
	RGuiItem*	pguiRes		= NULL;	// Assume nothing.
	int16_t	sError	= 0;

	// Allocate a new item . . .
	pguiRes	= new RGuiItem;
	if (pguiRes != NULL)
		{
		// Copy parms to new item.
		CopyParms(pguiRes);

		// Set callback.
		pguiRes->m_bcUser				= PressedCall;
		pguiRes->m_ulUserInstance	= (U64)this;

		// Get thickness of border.
		int16_t sBorder	= pguiRes->GetTopLeftBorderThickness()
			+ pguiRes->GetBottomRightBorderThickness();

		// Get the width and height of the encapsulated item.
		int16_t	sWidth	= pgui->m_im.m_sWidth;
		int16_t	sHeight	= pgui->m_im.m_sHeight;

		// Adjust by border thickness.
		sWidth	+= sBorder;
		sHeight	+= sBorder;

		// Create item . . .
		if (pguiRes->Create(0, 0, sWidth, sHeight, m_im.m_sDepth) == 0)
			{
			// Make item a child of encapsulator.
			pgui->SetParent(pguiRes);
			// Mark item as an encapsulator.
			MakeEncapsulator(pguiRes);
			// Item position is not set until the next AdjustContents() call.
			}
		else
			{
			TRACE("CreateEncapsulator(): pguiRes->Create() failed.\n");
			sError	= 2;
			}

		// If any errors occurred after allocation . . .
		if (sError != 0)
			{
			delete pguiRes;
			pguiRes	= NULL;
			}
		}
	else
		{
		TRACE("CreateEncapsulator(): Failed to allocate RTxt.\n");
		sError	= 1;
		}

	return pguiRes;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Place item at specified location in list of container's child items.
// Under new pretenses, this should not fail.
//
//////////////////////////////////////////////////////////////////////////////
void RListBox::AddAfter(		// Returns nothing.
	RGuiItem*	pgui,				// Item to add.
	RGuiItem*	pguiAfter)		// Item to add after or NULL to add at
										// end.
	{
	// Reposition the new item.  Under new pretenses, this SHOULD not fail.
	m_lcContents.m_listguiChildren.Remove(pgui);

	// If none specified . . .
	if (pguiAfter == NULL)
		{
		// Insert new item.  Under new pretenses, this SHOULD not fail.
		m_lcContents.m_listguiChildren.AddTail(pgui);
		}
	else
		{
		// Insert new item.  Under new pretenses, this SHOULD not fail.
		m_lcContents.m_listguiChildren.InsertAfter(pguiAfter, pgui);
		}
	}

//////////////////////////////////////////////////////////////////////////////
//
// Place item at specified location in list of container's child items.
// Under new pretenses, this should not fail.
//
//////////////////////////////////////////////////////////////////////////////
void RListBox::InsertBefore(	// Returns nothing.
	RGuiItem*	pgui,				// Item to insert.
	RGuiItem*	pguiBefore)		// Item to insert before or NULL to insert
										// at beginning.
	{
	// Reposition the new item.  Under new pretenses, this SHOULD not fail.
	m_lcContents.m_listguiChildren.Remove(pgui);

	// If none specified . . .
	if (pguiBefore == NULL)
		{
		// Insert new item.  Under new pretenses, this SHOULD not fail.
		m_lcContents.m_listguiChildren.InsertHead(pgui);
		}
	else
		{
		// Insert new item.  Under new pretenses, this SHOULD not fail.
		m_lcContents.m_listguiChildren.InsertBefore(pguiBefore, pgui);
		}
	}

//////////////////////////////////////////////////////////////////////////////
//
// [Un]Select the specified item.
//
//////////////////////////////////////////////////////////////////////////////
void RListBox::SelectItem(		// Returns nothing.
	RGuiItem*	pguiSel,			// Item to [un]select.
	int16_t			sSelect)			// If TRUE, item is selected; if FALSE,
										// item is unselected.
	{
	// If there was previously a selection . . .
	if (pguiSel != NULL)
		{
		// If this is an encapsulation . . .
		if (IsEncapsulator(pguiSel) != FALSE)
			{
			// Toggle border.
			pguiSel->m_sInvertedBorder	= sSelect;
			// Recompose item with new border effect.
			pguiSel->Compose();
			}
		}
	}

////////////////////////////////////////////////////////////////////////
//
// Load item's children from the specified file.
// (virtual override).
// (protected).
//
////////////////////////////////////////////////////////////////////////
int16_t RListBox::LoadChildren(	// Returns 0 on success.
	RFile*	pfile)				// File to load from.
	{
	int16_t	sRes	= 0;	// Assume success.

	ASSERT(pfile->IsOpen() != FALSE);

	int16_t	sNum;
	// Read number of children.
	pfile->Read(&sNum);

	// The first three are our scrollbars and frame.
	ASSERT(sNum >= 3);

	// Load directly into these special children.
	if (m_sbVert.Load(pfile) == 0)
		{
		if (m_sbHorz.Load(pfile) == 0)
			{
			// m_lcContents needs to know its parent during Load() (specifically
			// LoadChildren() needs to know).
			m_lcContents.SetParent(this);

			if (m_lcContents.Load(pfile) == 0)
				{
				// Subtract these three children from total.
				sNum	-= 3;
				}
			else
				{
				TRACE("LoadChildren(): m_sbVert.Load() failed.\n");
				sRes	= -3;
				}
			}
		else
			{
			TRACE("LoadChildren(): m_sbHorz.Load() failed.\n");
			sRes	= -2;
			}
		}
	else
		{
		TRACE("LoadChildren(): m_lcContents.Load() failed.\n");
		sRes	= -1;
		}

	// Instantiate rest of children.
	RGuiItem* pgui;
	int16_t	sCurChild;
	for (	sCurChild	= 0; 
			sCurChild < sNum && sRes == 0 && pfile->Error() == FALSE; 
			sCurChild++)
		{
		pgui	= LoadInstantiate(pfile);
		if (pgui != NULL)
			{
			pgui->SetParent(this);
			}
		else
			{
			TRACE("LoadChildren(): LoadInstantiate() failed.\n");
			sRes	= -1;
			}
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Save item's children to the specified file.
// (virtual override).
// (protected).
//
////////////////////////////////////////////////////////////////////////
int16_t RListBox::SaveChildren(	// Returns 0 on success.
	RFile*	pfile)				// File to save to.
	{
	int16_t	sRes	= 0;	// Assume success.

	ASSERT(pfile->IsOpen() != FALSE);

	// Determine number of child items.
	int16_t	sNum	= 0;
	RGuiItem*	pgui = m_listguiChildren.GetHead();
	while (pgui != NULL)
		{
		sNum++;

		pgui	= m_listguiChildren.GetNext();
		}

	// Write number of children.
	pfile->Write(sNum);

	// These should definitely be children of 'this' item.
	ASSERT(m_sbVert.GetParent()		== this);
	ASSERT(m_sbHorz.GetParent()		== this);
	ASSERT(m_lcContents.GetParent()	== this);

	// Always write our 3 special children (scrollbars & frame) first so we know
	// where to get them on load.
	if (m_sbVert.Save(pfile) == 0)
		{
		if (m_sbHorz.Save(pfile) == 0)
			{
			if (m_lcContents.Save(pfile) == 0)
				{
				// Subtract these three children from total.
				// Currently this number is not used during save,
				// but just in case it ever is.
				sNum	-= 3;
				}
			else
				{
				TRACE("SaveChildren(): m_lcContents.Save() failed.\n");
				sRes	= -3;
				}
			}
		else
			{
			TRACE("SaveChildren(): m_sbHorz.Save() failed.\n");
			sRes	= -2;
			}
		}
	else
		{
		TRACE("SaveChildren(): m_sbVert.Save() failed.\n");
		sRes	= -1;
		}

	// Save children.  Note that we go through the children in reverse
	// order so they, on load, get added back to their parent in the
	// order they were originally added to this parent.
	pgui	= m_listguiChildren.GetTail();
	while (pgui != NULL && sRes == 0 && pfile->Error() == FALSE)
		{
		// Don't write these 3 again . . .
		if (pgui != &m_sbVert && pgui != &m_sbHorz && pgui != &m_lcContents)
			{
			// Save child.
			sRes	= pgui->Save(pfile);
			}

		pgui	= m_listguiChildren.GetPrev();
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Read item's members from file.
// (virtual/protected (overriden here)).
//
////////////////////////////////////////////////////////////////////////
int16_t RListBox::ReadMembers(	// Returns 0 on success.
	RFile*	pfile,					// File to read from.
	U32		u32Version)				// File format version to use.
	{
	int16_t	sRes	= 0;	// Assume success.

	// Invoke base class to read base members.
	sRes	= RGuiItem::ReadMembers(pfile, u32Version);

	// If okay so far . . .
	if (sRes == 0)
		{
		ASSERT(pfile != NULL);
		ASSERT(pfile->IsOpen() != FALSE);
		
		U32	u32Temp;
		
		// Switch on version.
		switch (u32Version)
			{
			default:
			// Insert additional version numbers here!
			// case 4:	// Version 4 stuff.
			// case 3:	// Version 3 stuff.
			case 2:	// Version 2 added encapsulator type.
				pfile->Read(&u32Temp);
				m_typeEncapsulator	= (Type)u32Temp;

			case 1:
				// Read this class's members.
				pfile->Read(&u32Temp);
				m_sbvVert	= (ScrollBarVisibility)u32Temp;

				pfile->Read(&u32Temp);
				m_sbvHorz	= (ScrollBarVisibility)u32Temp;

			case 0:	// In version 0, only base class RGuiItem members were stored.
				// If successful . . .
				if (pfile->Error() == FALSE)
					{
					// Success.
					}
				else
					{
					TRACE("ReadMembers(): Error reading RListBox members.\n");
					sRes	= -1;
					}
				break;
			}
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
//
// Write item's members to file.
// (virtual/protected (overriden here)).
//
////////////////////////////////////////////////////////////////////////
int16_t RListBox::WriteMembers(	// Returns 0 on success.
	RFile*	pfile)					// File to write to.
	{
	int16_t	sRes	= 0;	// Assume success.

	// Invoke base class to read base members.
	sRes	= RGuiItem::WriteMembers(pfile);

	// If okay so far . . .
	if (sRes == 0)
		{
		ASSERT(pfile != NULL);
		ASSERT(pfile->IsOpen() != FALSE);
		

		// Write this class's members.
		////////////// Version 2 ////////////////////
		pfile->Write((U32)m_typeEncapsulator);	
		////////////// Version 1 ////////////////////
		pfile->Write((U32)m_sbvVert);
		pfile->Write((U32)m_sbvHorz);
		////////////// Version 0 ////////////////////

		// If successful . . .
		if (pfile->Error() == FALSE)
			{
			// Success.
			}
		else
			{
			TRACE("WriteMembers(): Error writing RListBox members.\n");
			sRes	= -1;
			}
		}

	return sRes;
	}

//////////////////////////////////////////////////////////////////////////////
// EOF
//////////////////////////////////////////////////////////////////////////////
