////////////////////////////////////////////////////////////////////////////////
//
// 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
//
//////////////////////////////////////////////////////////////////////////////
//
// MultiBtn.cpp
// 
// History:
//		04/10/97 JMI	Started this using RPushBtn as a template.
//
//		04/17/97	JMI	Added Load and Save components.
//
//		04/22/97	JMI	Added NextState().
//							CursorEvent() now uses NextState().
//							Also, DrawBackgroundRes() now chooses the image indexed
//							by m_sState instead of m_sState + 1.
//
//		09/25/97	JMI	ReadMembers() was not clearing states that had no 
//							corresponding images which, since SetNumStates() 
//							preserves existing state images, could result in old 
//							images persisting through loads that contained no image
//							for that state.
//							Also, now, in file version 7, reads and writes the 
//							current state.
//
//////////////////////////////////////////////////////////////////////////////
//
// This a GUI item that is based on RBtn. 
// This overrides CursorEvent() to get information about where a click in its
// RHot occurred.
// This overrides Compose() to add text.
//
// Enhancements/Uses:
// To change the look of a button when pressed, you may want to override the
// Compose() or DrawBorder() in a derived class.
// To get a callback on a click/release pair in the button, set m_bcUser.
//
//////////////////////////////////////////////////////////////////////////////

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

#ifdef PATHS_IN_INCLUDES
	#include "ORANGE/GUI/MultiBtn.h"
#else
	#include "multibtn.h"
#endif // PATHS_IN_INCLUDES

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

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

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

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

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

//////////////////////////////////////////////////////////////////////////////
//
// Default constructor.
//
//////////////////////////////////////////////////////////////////////////////
RMultiBtn::RMultiBtn()
	{
	// Override RGuiItem's/RBtn's defaults.
	m_type				= MultiBtn;	// Indicates type of GUI item.

	// Initialize RMultiBtn members.
	m_sState				= 0;		// The button's current state, 0..m_sNumStates - 1.
	m_sNumStates		= 0;		// Number of button states.                        
	m_papimStates		= NULL;	// Ptr to array of m_sNumStates ptrs to button     
										// state images.                                   
	}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor.
//
//////////////////////////////////////////////////////////////////////////////
RMultiBtn::~RMultiBtn()
	{
	DestroyStates();
	}

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

////////////////////////////////////////////////////////////////////////
//
// Cursor event notification.
// Events in event area.
// (virtual).
//
////////////////////////////////////////////////////////////////////////
void RMultiBtn::CursorEvent(	// Returns nothing.
	RInputEvent* pie)				// In:  Most recent user input event.             
										// Out: pie->sUsed = TRUE, if used.
	{
	switch (pie->sEvent)
		{
		case RSP_MB0_DOUBLECLICK:
		case RSP_MB0_RELEASED:
			// If we were clicked in . . .
			if (m_sPressed != FALSE)
				{
				// Do change of state right away so user callback gets the new
				// value.
				// If within event area . . .
				if (		pie->sPosX >= m_sEventAreaX && pie->sPosX < m_sEventAreaX + m_sEventAreaW
						&&	pie->sPosY >= m_sEventAreaY && pie->sPosY < m_sEventAreaY + m_sEventAreaH)
					{
					// Change state.
					NextState();
					}
				}

			break;
		}

	// Call base.
	RGuiItem::CursorEvent(pie);

	switch (pie->sEvent)
		{
		case RSP_MB0_DOUBLECLICK:
		case RSP_MB0_PRESSED:
			// Always recompose on press, since there's so many possibilities
			// with this button.
			Compose();

			// Note that we used it.
			pie->sUsed	= TRUE;

			break;

		case RSP_MB0_RELEASED:
			// Always recompose on release, since there's so many possibilities
			// with this button.
			Compose();

			// Note that we used it.
			pie->sUsed	= TRUE;

			break;
		}
	}

////////////////////////////////////////////////////////////////////////
// Draw background resource, if one is specified.
// Utilizes base class version to place and BLiT the resource.
// (virtual).
////////////////////////////////////////////////////////////////////////
void RMultiBtn::DrawBackgroundRes(	// Returns nothing.
	RImage* pim /*= NULL*/)				// Dest image, uses m_im, if NULL.
	{
	// Store old bkd res.
	RImage*	pimBkdRes	= m_pimBkdRes;

	// If we have any states . . .
	if (m_papimStates != NULL)
		{
		// Choose proper image.
		if (m_sPressed == FALSE)
			{
			// If the state is available . . .
			if (m_sState <= m_sNumStates)
				{
				// Get the state.
				m_pimBkdRes	= m_papimStates[m_sState];
				}
			}
		else
			{
			// Get the pressed feedback.
			m_pimBkdRes	= m_papimStates[0];
			}
		}

	// Call base.
	RBtn::DrawBackgroundRes(pim);

	// Restore bkd res.
	m_pimBkdRes	= pimBkdRes;
	}

////////////////////////////////////////////////////////////////////////
// Set number of states.
// This will clear all existing state images.
////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::SetNumStates(	// Returns 0 on success.
	int16_t sNumStates)					// In:  New number of states.
	{
	int16_t	sRes	= 0;	// Assume success.

	// Allocate an array of image ptrs and clear them all . . .
	RImage** papimNewStates	= new RImage*[sNumStates + 1];
	if (papimNewStates != NULL)
		{
		// Clear all the ptrs to NULL.
		memset(papimNewStates, 0, sizeof(RImage*) * (sNumStates + 1));

		// If there was an old array . . .
		if (m_papimStates != NULL)
			{
			// Copy any currently valid ptrs within new range.
			int16_t	i;
			for (i = 0; i <= sNumStates && i <= m_sNumStates; i++)
				{
				// Copy entry.
				papimNewStates[i]	= m_papimStates[i];
				// Clear entry so it is not deleted.
				m_papimStates[i]	= NULL;
				}

			// Destroy any current states plus array.
			DestroyStates();
			}

		// Store the new number of states.
		m_sNumStates	= sNumStates;
		// Store new arrray.
		m_papimStates	= papimNewStates;
		}
	else
		{
		TRACE("SetNumStates(): Failed to allocate new array of Image ptrs.\n");
		sRes	= -1;
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
// Set button state or feedback state image.
////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::SetState(	// Returns 0 on success.
	RImage*	pim,				// In:  Image for state sState.
	int16_t		sState)			// In:  State to update (0 == feedback state,
									// 1..n == state number).
	{
	int16_t	sRes	= 0;	// Assume success.

	if (m_papimStates == NULL || sState >= m_sNumStates)
		{
		sRes = SetNumStates(sState);
		}

	// If successful so far . . .
	if (sRes == 0)
		{
		// Clear current value.
		delete m_papimStates[sState];

		// Allocate new one . . .
		m_papimStates[sState]	= new RImage;
		if (m_papimStates[sState] != NULL)
			{
			// Copy specified image.
			*(m_papimStates[sState])	= *pim;
			}
		else
			{
			TRACE("SetState():  Failed to allocate new RImage.\n");
			sRes	= -1;
			}
		}

	return sRes;
	}

//////////////////////////////////////////////////////////////////////////////
// Set button state or feedback state image.
// The feedback state image is always the last image (m_sNumStates).
//////////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::SetState(	// Returns 0 on success.
	char*	pszImageName,		// In:  File name of image for state sState.
	int16_t		sState)			// In:  State to update (0 == feedback state,
									// 1..n == state number).
	{
	int16_t	sRes	= 0;	// Assume success.

	RImage	im;
	if (RFileEZLoad(&im, pszImageName, "rb", RFile::LittleEndian) == 0)
		{
		sRes	= SetState(&im, sState);
		}
	else
		{
		TRACE("SetState():  RFileEZLoad() failed for \"%s\".\n", pszImageName);
		sRes	= -1;
		}
	
	return sRes;
	}

//////////////////////////////////////////////////////////////////////////////
// Clear button state or feedback state image.
// The feedback state image is always the first image.
//////////////////////////////////////////////////////////////////////////////
void RMultiBtn::ClearState(	// Returns nothing.
	int16_t	sState)					// In:  State to clear (0 == feedback state,
										// 1..n == state number).
	{
	if (sState >= 0 && sState <= m_sNumStates)
		{
		if (m_papimStates != NULL)
			{
			// State be gone.  Safe for already deallocated states as long as
			// they're NULL.
			delete m_papimStates[sState];
			m_papimStates[sState]	= NULL;
			}
		}
	}

//////////////////////////////////////////////////////////////////////////////
// Go to the next logical state.
//////////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::NextState(void)	// Returns new state.
	{
	if (m_sNumStates > 0)
		{
		m_sState	= (m_sState % m_sNumStates) + 1;
		}
	else
		{
		m_sState	= 0;
		}

	return m_sState;
	}

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

//////////////////////////////////////////////////////////////////////////////
// Get the current image for the specified state.
//////////////////////////////////////////////////////////////////////////////
RImage* RMultiBtn::GetState(	// Returns image, if available; NULL, otherwise.
	int16_t	sState)					// In:  State to get (0 == feedback state,
										// 1..n == state number).
	{
	RImage*	pimRes	= NULL;	// Assume not available.

	if (sState >= 0 && sState <= m_sNumStates)
		{
		if (m_papimStates != NULL)
			{
			pimRes	= m_papimStates[sState];
			}
		}

	return pimRes;
	}

//////////////////////////////////////////////////////////////////////////////
// Internal functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Destroys current state bitmaps.
//////////////////////////////////////////////////////////////////////////////
void RMultiBtn::DestroyStates(void)	// Returns nothing.
	{
	if (m_papimStates != NULL)
		{
		int16_t i;
		for (i = 0; i <= m_sNumStates; i++)
			{
			delete m_papimStates[i];
			}

		delete []m_papimStates;

		m_sNumStates	= 0;
		}
	}

////////////////////////////////////////////////////////////////////////
// Read item's members from file.
// (virtual/protected (overriden here)).
////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::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	= RBtn::ReadMembers(pfile, u32Version);

	// If okay so far . . .
	if (sRes == 0)
		{
		ASSERT(pfile != NULL);
		ASSERT(pfile->IsOpen() != FALSE);
		
		// Switch on version.
		switch (u32Version)
			{
			default:
			// Insert additional version numbers here!
			case 7:
				pfile->Read(&m_sState);
			case 6:
			case 5:
			case 4:
			case 3:
			case 2:

			case 1:
				{
				int16_t	sNumStates	= 0;	// Safety.
				// Read this class's members.
				pfile->Read(&sNumStates);
				// Set number of states.
				if (SetNumStates(sNumStates) == 0)
					{
					// Read all the images.
					int16_t	sCurState;
					int16_t	sImageForState;
					for (sCurState = 0; sCurState <= m_sNumStates && sRes == 0; sCurState++)
						{
						pfile->Read(&sImageForState);
						if (sImageForState != FALSE)
							{
							// There is an image.  Load it.
							RImage	imState;
							if (imState.Load(pfile) == 0)
								{
								// Set that state.
								if (SetState(&imState, sCurState) == 0)
									{
									// Successfully loaded and set state image.
									}
								else
									{
									TRACE("ReadMembers9): SetState() failed for state #%d.\n", sCurState);
									sRes	= -3;
									}
								}
							else
								{
								TRACE("ReadMembers(): GetState() failed for state #%d.\n", sCurState);
								sRes	= -2;
								}
							}
						else
							{
							// Make sure the state is clear.
							ClearState(sCurState);
							}
						}
					}
				else
					{
					TRACE("ReadMembers(): SetNumStates() failed.\n");
					sRes	= -1;
					}
				}


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

	return sRes;
	}

////////////////////////////////////////////////////////////////////////
// Write item's members to file.
// (virtual/protected (overriden here)).
////////////////////////////////////////////////////////////////////////
int16_t RMultiBtn::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	= RBtn::WriteMembers(pfile);

	// If okay so far . . .
	if (sRes == 0)
		{
		ASSERT(pfile != NULL);
		ASSERT(pfile->IsOpen() != FALSE);
		
		// Write this class's members.
		pfile->Write(m_sState);
		pfile->Write(m_sNumStates);
		// Write all the images.
		int16_t	sCurState;
		for (sCurState = 0; sCurState <= m_sNumStates && sRes == 0; sCurState++)
			{
			// If there is a bitmap for this state . . .
			RImage*	pimState	= GetState(sCurState);
			if (pimState != NULL)
				{
				// There is an image.  Write flag indicating such.
				pfile->Write((int16_t)TRUE);
				// Write image.
				sRes	= pimState->Save(pfile);
				}
			else
				{
				// No image.  Write flag indicating such.
				pfile->Write((int16_t)FALSE);
				}
			}

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

	return sRes;
	}

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