/******************************************************************************
 JXChooseSaveFile.cc

	JX class to let user easily select and save files.

	BASE CLASS = JChooseSaveFile, virtual JBroadcaster

	Copyright © 1996-98 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <JXChooseSaveFile.h>
#include <JXChooseFileDialog.h>
#include <JXChoosePathDialog.h>
#include <JXSaveFileDialog.h>
#include <JXWindow.h>
#include <jXGlobals.h>
#include <JUNIXDirInfo.h>
#include <jDirUtil.h>
#include <jStrStreamUtil.h>
#include <jAssert.h>

/******************************************************************************
 Constructor

 ******************************************************************************/

JXChooseSaveFile::JXChooseSaveFile()
	:
	JChooseSaveFile(),
	JBroadcaster()
{
	itsUserFilter = new JString;
	assert( itsUserFilter != NULL );

	itsDirInfo           = NULL;	// constructed in GetDirInfo()
	itsCurrentDialog     = NULL;
	itsChooseFileDialog  = NULL;
	itsChoosePathDialog  = NULL;
	itsSaveFileDialog    = NULL;

	itsResultStr  = NULL;
	itsResultList = NULL;

	itsDialogState = new JString;
	assert( itsDialogState != NULL );
}

/******************************************************************************
 Destructor

 ******************************************************************************/

JXChooseSaveFile::~JXChooseSaveFile()
{
	delete itsDirInfo;
	delete itsUserFilter;
	delete itsDialogState;
}

/******************************************************************************
 ChooseFile

	Displays the prompt and asks the user to choose a file.

	Returns kFalse if user cancels.

	To display only files with a specific cookie, override
	SetChooseFileContentFilter().

 ******************************************************************************/

JBoolean
JXChooseSaveFile::ChooseFile
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	JString*			fullName
	)
{
	itsResultStr = fullName;
	return ChooseFile(prompt, instructions, NULL, kFalse);
}

JBoolean
JXChooseSaveFile::ChooseFile
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	const JCharacter*	origName,
	JString*			fullName
	)
{
	itsResultStr = fullName;
	return ChooseFile(prompt, instructions, origName, kFalse);
}

JBoolean
JXChooseSaveFile::ChooseFiles
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	JPtrArray<JString>*	fullNameList
	)
{
	itsResultList = fullNameList;
	return ChooseFile(prompt, instructions, NULL, kTrue);
}

// private

JBoolean
JXChooseSaveFile::ChooseFile
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	const JCharacter*	originalName,
	const JBoolean		allowSelectMultiple
	)
{
	JUNIXDirInfo* dirInfo   = GetDirInfo();
	JBoolean restorePath    = kFalse;
	const JString savedPath = dirInfo->GetCWD();

	JString origName;
	if (!JStringEmpty(originalName) && strchr(originalName, '/') != NULL)
		{
		JString path;
		JSplitPathAndName(originalName, &path, &origName);
		dirInfo->GoToClosest(path);
		restorePath = kTrue;
		if (!JSameDirEntry(path, dirInfo->GetCWD()))
			{
			origName.Clear();
			}
		}

	assert( itsChooseFileDialog == NULL );

	(JXGetApplication())->PrepareForBlockingWindow();

	itsChooseFileDialog =
		CreateChooseFileDialog(JXGetApplication(), GetDirInfo(),
							   *itsUserFilter, origName, allowSelectMultiple,
							   instructions);

	SetChooseFileContentFilter(GetDirInfo());
	RestoreState(itsChooseFileDialog);
	WaitForResponse(itsChooseFileDialog);

	if (restorePath)
		{
		dirInfo->GoTo(savedPath);
		}

	itsChooseFileDialog = NULL;
	return itsResponse;
}

// virtual protected

JXChooseFileDialog*
JXChooseSaveFile::CreateChooseFileDialog
	(
	JXDirector*			supervisor,
	JUNIXDirInfo*		dirInfo,
	const JCharacter*	fileFilter,
	const JCharacter*	origName,
	const JBoolean		allowSelectMultiple,
	const JCharacter*	message
	)
{
	JXChooseFileDialog* dlog =
		new JXChooseFileDialog(supervisor, dirInfo, fileFilter, allowSelectMultiple);
	assert( dlog != NULL );
	dlog->BuildWindow(origName, message);
	return dlog;
}

void
JXChooseSaveFile::SetChooseFileContentFilter
	(
	JUNIXDirInfo* dirInfo
	)
{
	dirInfo->ClearContentFilter();
}

/******************************************************************************
 ChooseRPath

	Displays the prompt and asks the user to choose a readable path.

	Returns kFalse if user cancels.

 ******************************************************************************/

JBoolean
JXChooseSaveFile::ChooseRPath
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	const JCharacter*	origPath,
	JString*			newPath
	)
{
	return ChoosePath(kFalse, instructions, origPath, newPath);
}

/******************************************************************************
 ChooseRWPath

	Displays the prompt and asks the user to choose a writable path.

	Returns kFalse if user cancels.

 ******************************************************************************/

JBoolean
JXChooseSaveFile::ChooseRWPath
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	const JCharacter*	origPath,
	JString*			newPath
	)
{
	return ChoosePath(kTrue, instructions, origPath, newPath);
}

/******************************************************************************
 ChoosePath (private)

	origPath can be NULL.

 ******************************************************************************/

JBoolean
JXChooseSaveFile::ChoosePath
	(
	const JBoolean		selectOnlyWritable,
	const JCharacter*	instructions,
	const JCharacter*	origPath,
	JString*			newPath
	)
{
	itsResultStr = newPath;

	JUNIXDirInfo* dirInfo   = GetDirInfo();
	JBoolean restorePath    = kFalse;
	const JString savedPath = dirInfo->GetCWD();

	if (!JStringEmpty(origPath))
		{
		dirInfo->GoToClosest(origPath);
		restorePath = kTrue;
		}

	assert( itsChoosePathDialog == NULL );

	(JXGetApplication())->PrepareForBlockingWindow();

	itsChoosePathDialog =
		CreateChoosePathDialog(JXGetApplication(), dirInfo, *itsUserFilter,
							   selectOnlyWritable, instructions);

	(GetDirInfo())->ClearContentFilter();
	RestoreState(itsChoosePathDialog);
	WaitForResponse(itsChoosePathDialog);

	if (restorePath)
		{
		dirInfo->GoTo(savedPath);
		}

	itsChoosePathDialog = NULL;
	return itsResponse;
}

// virtual protected

JXChoosePathDialog*
JXChooseSaveFile::CreateChoosePathDialog
	(
	JXDirector*			supervisor,
	JUNIXDirInfo*		dirInfo,
	const JCharacter*	fileFilter,
	const JBoolean		selectOnlyWritable,
	const JCharacter*	message
	)
{
	JXChoosePathDialog* dlog =
		new JXChoosePathDialog(supervisor, dirInfo, fileFilter,
							   selectOnlyWritable);
	assert( dlog != NULL );
	dlog->BuildWindow(message);
	return dlog;
}

/******************************************************************************
 SaveFile

	Displays the prompts and asks the user for a file name.

	Returns kFalse if user cancels.

 ******************************************************************************/

JBoolean
JXChooseSaveFile::SaveFile
	(
	const JCharacter*	prompt,
	const JCharacter*	instructions,
	const JCharacter*	originalName,
	JString*			newFullName
	)
{
	itsResultStr = newFullName;

	JUNIXDirInfo* dirInfo   = GetDirInfo();
	JBoolean restorePath    = kFalse;
	const JString savedPath = dirInfo->GetCWD();

	JString origName = originalName;
	if (origName.Contains("/"))
		{
		JString path;
		JSplitPathAndName(originalName, &path, &origName);
		dirInfo->GoToClosest(path);
		restorePath = kTrue;
		}

	assert( itsSaveFileDialog == NULL );

	(JXGetApplication())->PrepareForBlockingWindow();

	itsSaveFileDialog =
		CreateSaveFileDialog(JXGetApplication(), dirInfo, *itsUserFilter,
							 origName, prompt, instructions);

	(GetDirInfo())->ClearContentFilter();
	RestoreState(itsSaveFileDialog);
	WaitForResponse(itsSaveFileDialog);

	if (restorePath)
		{
		dirInfo->GoTo(savedPath);
		}

	itsSaveFileDialog = NULL;
	return itsResponse;
}

// virtual protected

JXSaveFileDialog*
JXChooseSaveFile::CreateSaveFileDialog
	(
	JXDirector*			supervisor,
	JUNIXDirInfo*		dirInfo,
	const JCharacter*	fileFilter,
	const JCharacter*	origName,
	const JCharacter*	prompt,
	const JCharacter*	message
	)
{
	JXSaveFileDialog* dlog =
		new JXSaveFileDialog(supervisor, dirInfo, fileFilter);
	assert( dlog != NULL );
	dlog->BuildWindow(origName, prompt, message);
	return dlog;
}

/******************************************************************************
 Receive (protected)

	Listen for response from itsCurrentDialog.

 ******************************************************************************/

void
JXChooseSaveFile::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (sender == itsCurrentDialog && message.Is(JXDialogDirector::kDeactivated))
		{
		const JXDialogDirector::Deactivated* info =
			dynamic_cast(const JXDialogDirector::Deactivated*, &message);
		assert( info != NULL );
		itsResponse    = info->Successful();
		*itsUserFilter = itsCurrentDialog->GetFilter();
		SaveState(itsCurrentDialog);

		if (itsResponse && sender == itsChooseFileDialog && itsResultList != NULL)
			{
			const JBoolean ok = itsChooseFileDialog->GetFullNames(itsResultList);
			assert( ok );
			}
		else if (itsResponse && sender == itsChooseFileDialog)
			{
			assert( itsResultStr != NULL );
			const JBoolean ok = itsChooseFileDialog->GetFullName(itsResultStr);
			assert( ok );
			}
		else if (itsResponse && sender == itsChoosePathDialog)
			{
			assert( itsResultStr != NULL );
			*itsResultStr = itsChoosePathDialog->GetPath();
			}
		else if (itsResponse && sender == itsSaveFileDialog)
			{
			assert( itsResultStr != NULL );
			JString name;
			const JBoolean ok = itsSaveFileDialog->GetFileName(&name);
			assert( ok );
			*itsResultStr = JCombinePathAndName(itsSaveFileDialog->GetPath(), name);
			}
		else if (itsResultStr != NULL)
			{
			itsResultStr->Clear();
			}
		else if (itsResultList != NULL)
			{
			itsResultList->DeleteAll();
			}
		itsCurrentDialog = NULL;
		itsResultStr     = NULL;
		itsResultList    = NULL;
		}

	else
		{
		JBroadcaster::Receive(sender, message);
		}
}

/******************************************************************************
 WaitForResponse (private)

	Caller must call app->PrepareForBlockingWindow() before creating window.

 ******************************************************************************/

void
JXChooseSaveFile::WaitForResponse
	(
	JXCSFDialogBase* dlog
	)
{
	assert( itsCurrentDialog == NULL );

	itsCurrentDialog = dlog;
	ListenTo(itsCurrentDialog);
	itsCurrentDialog->BeginDialog();

	// display the inactive cursor in all the other windows

	JXApplication* app = JXGetApplication();
	app->DisplayInactiveCursor();

	// block with event loop running until we get a response

	JXWindow* window = itsCurrentDialog->GetWindow();
	while (itsCurrentDialog != NULL)
		{
		app->HandleOneEventForWindow(window);
		}

	app->BlockingWindowFinished();
}

/******************************************************************************
 RestoreState (private)

	Let the new dialog read its state from our saved data.

 ******************************************************************************/

void
JXChooseSaveFile::RestoreState
	(
	JXCSFDialogBase* dlog
	)
	const
{
	(dlog->GetWindow())->PlaceAsDialogWindow();

	JString* dialogState    = GetDialogState();
	const JSize stateLength = dialogState->GetLength();
	if (stateLength > 0)
		{
		jistrstream(input, dialogState->GetCString(), stateLength);
		dlog->ReadBaseSetup(input);
		}
}

/******************************************************************************
 SaveState (private)

	Let the dialog save its state in our saved data.

 ******************************************************************************/

void
JXChooseSaveFile::SaveState
	(
	JXCSFDialogBase* dlog
	)
{
	ostrstream output;
	dlog->WriteBaseSetup(output);
	output << ends;
	*(GetDialogState()) = output.str();
	JUnfreeze(output);
}

/******************************************************************************
 ReadSetup

	Read in setup information.  Derived classes of JXApplication can use
	this to restore our state from the previous time the program was run.

 ******************************************************************************/

void
JXChooseSaveFile::ReadSetup
	(
	istream& input
	)
{
	input >> *(GetDialogState());
}

/******************************************************************************
 WriteSetup

	Write setup information.  Derived classes of JXApplication can use this
	to save our state for the next time the program is run.

 ******************************************************************************/

void
JXChooseSaveFile::WriteSetup
	(
	ostream& output
	)
	const
{
	output << *(GetDialogState());
}

/******************************************************************************
 GetDirInfo (protected)

	Since JUNIXDirInfo requires a progress display, we can't create it
	until after -all- the globals have been initialized.  We are one of
	the globals, so we can't create it in our constructor.

	This also provides a convenient place to insure that all other CSF
	objects use the same JUNIXDirInfo object.

 ******************************************************************************/

JUNIXDirInfo*
JXChooseSaveFile::GetDirInfo()
{
	if (itsDirInfo == NULL && this == JXGetChooseSaveFile())
		{
		JString dirName = JGetCurrentDirectory();
		if (!JUNIXDirInfo::Create(dirName, &itsDirInfo))
			{
			JBoolean ok = JGetHomeDirectory(&dirName);
			if (!ok || !JUNIXDirInfo::Create(dirName, &itsDirInfo))
				{
				dirName = "/";
				ok = JUNIXDirInfo::Create(dirName, &itsDirInfo);
				assert( ok );
				}
			}
		itsDirInfo->ShowHidden(kFalse);
		return itsDirInfo;
		}
	else if (itsDirInfo == NULL)
		{
		return (JXGetChooseSaveFile())->GetDirInfo();
		}
	else
		{
		return itsDirInfo;
		}
}

/******************************************************************************
 GetDialogState (private)

	Since JUNIXDirInfo requires a progress display, we can't create it
	until after -all- the globals have been initialized.  We are one of
	the globals, so we can't create it in our constructor.

	This also provides a convenient place to insure that all other CSF
	objects use the same JUNIXDirInfo object.

 ******************************************************************************/

JString*
JXChooseSaveFile::GetDialogState()
	const
{
	if (this == JXGetChooseSaveFile())
		{
		return itsDialogState;
		}
	else
		{
		return (JXGetChooseSaveFile())->GetDialogState();
		}
}
