/******************************************************************************
 JXHelpManager.cc

	This class is designed to be used as a global object.  All requests by
	the user to display help information should be passed to this object.
	It creates and maintains JXHelpDirectors to display the text.

	Every help section needs to be registered by calling RegisterSection().
	The name is a unique string that identifies the section.  The title is
	used as the window title.  All three strings must be stored in static
	data because we do not want to have to copy them, especially the
	text itself.  The format of the text must be HTML.

	After being registered, all requests to display the section are done by
	name.  This allows JXHelpText to handle hypertext links by parsing URL's
	of the form <a href="jxhelp:name_of_section#name_of_subsection">.  All
	other URL's are passed to user specified commands (usually netscape).

	PrintAll() is not supported because hypertext does not work well on
	paper.  Instead, one should provide an equivalent PostScript file with
	a Table of Contents that lists page numbers.  This PostScript file can
	also be considerably spruced up with headers, footers, graphics, and
	an index.

	BASE CLASS = JXDirector

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

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

#include <JXHelpManager.h>
#include <JXHelpDirector.h>
#include <JXEditHelpPrefsDialog.h>
#include <JXDisplay.h>
#include <JXWindow.h>
#include <JXPSPrinter.h>
#include <jXGlobals.h>
#include <JSimpleProcess.h>
#include <JSubstitute.h>
#include <jAssert.h>

static const JCharacter* kJXHelpPrefix = "jxhelp:";
const JSize kJXHelpPrefixLength        = 7;

static const JCharacter* kDefViewURLCmd = "netscape -remote \"openURL($u)\"";
static const JCharacter* kURLVarName    = "u";

static const JCharacter* kMailPrefix         = "mailto:";
const JSize kMailPrefixLength                = 7;
static const JCharacter* kDefSendMailCmd     = "netscape -remote \"mailto($a)\"";
static const JCharacter* kMailAddressVarName = "a";

static const JCharacter* kSubsectionMarker = "#";
const JSize kSubsectionMarkerLength        = 1;

// setup information

const JFileVersion kCurrentSetupVersion = 3;

	// version 1 stores itsDefWindowGeom
	// version 2 converts variable marker from % to $
	// version 3 stores PS print setup

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

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

JXHelpManager::JXHelpManager
	(
	const JXMenu::Style menuStyle
	)
	:
	JXDirector(NULL),
	itsMenuStyle(menuStyle),
	itsTOCSectionName(NULL)
{
	itsSections = new JArray<SectionInfo>;
	assert( itsSections != NULL );
	itsSections->SetCompareFunction(CompareSections);

	itsViewURLCmd = new JString(kDefViewURLCmd);
	assert( itsViewURLCmd != NULL );

	itsSendMailCmd = new JString(kDefSendMailCmd);
	assert( itsSendMailCmd != NULL );

	itsDefWindowGeom = new JString;
	assert( itsDefWindowGeom != NULL );

	JXDisplay* display = (JXGetApplication())->GetCurrentDisplay();
	itsPrinter = new JXPSPrinter(display, display->GetColormap());
	assert( itsPrinter != NULL );

	itsPrefsDialog = NULL;
}

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

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

JXHelpManager::~JXHelpManager()
{
	delete itsSections;		// JXHelpDirectors deleted by JXDirector
	delete itsViewURLCmd;
	delete itsSendMailCmd;
	delete itsDefWindowGeom;
	delete itsPrinter;
}

/******************************************************************************
 RegisterSection

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

void
JXHelpManager::RegisterSection
	(
	const JCharacter*	name,
	const JCharacter*	title,
	const JCharacter*	text
	)
{
	SectionInfo info(name, title, text);

	JBoolean isDuplicate;
	const JIndex i =
		itsSections->GetInsertionSortIndex(info, &isDuplicate);
	assert( !isDuplicate );

	itsSections->InsertElementAtIndex(i, info);
}

/******************************************************************************
 ShowTOC

	The first registered section is assumed to be the Table of Contents.

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

void
JXHelpManager::ShowTOC()
{
	if (itsTOCSectionName != NULL)
		{
		ShowSection(itsTOCSectionName);
		}
	else
		{
		(JGetUserNotification())->ReportError(
			"There is no Table of Contents.  Please notify the programmer.");
		}
}

/******************************************************************************
 ShowSection

	The JXHelpDirector* can be NULL.  If it isn't, and the section name is
	"#name_of_subsection", the director is searched for the subsection.

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

void
JXHelpManager::ShowSection
	(
	const JCharacter*	origName,
	JXHelpDirector*		helpDir
	)
{
	// check for subsection name

	JString name = origName;
	JString subsectionName;
	JIndex subIndex;
	if (name.LocateSubstring(kSubsectionMarker, &subIndex))
		{
		if (subIndex + kSubsectionMarkerLength <= name.GetLength())
			{
			subsectionName =
				name.GetSubstring(subIndex + kSubsectionMarkerLength,
								  name.GetLength());
			}
		name.RemoveSubstring(subIndex, name.GetLength());
		}

	// find section name

	SectionInfo target;
	target.name = name;

	if (name.IsEmpty())
		{
		if (helpDir == NULL)
			{
			return;
			}
		target.dir = helpDir;
		}
	else
		{
		JIndex i;
		if (!itsSections->SearchSorted(target, JOrderedSetT::kAnyMatch, &i))
			{
			JString msg = "Unknown help section \"";
			msg += name;
			msg += "\".  Please notify the programmer.";
			(JGetUserNotification())->ReportError(msg);
			return;
			}

		// display section

		target = itsSections->GetElement(i);
		if (target.dir == NULL)
			{
			target.dir = new JXHelpDirector(target.title, target.text,
											itsMenuStyle, itsPrinter);
			assert( target.dir != NULL );

			itsSections->SetElement(i, target);

			if (!itsDefWindowGeom->IsEmpty())
				{
				((target.dir)->GetWindow())->ReadGeometry(*itsDefWindowGeom);
				}
			else
				{
				((target.dir)->GetWindow())->PlaceAsDialogWindow();
				}
			}
		}

	(target.dir)->ShowSubsection(subsectionName);
	(target.dir)->Activate();
}

/******************************************************************************
 ShowURL

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

void
JXHelpManager::ShowURL
	(
	const JCharacter*	url,
	JXHelpDirector*		helpDir
	)
{
	// check for jxhelp

	JString s = url;
	if (s.BeginsWith(kJXHelpPrefix, kFalse))
		{
		s.RemoveSubstring(1, kJXHelpPrefixLength);
		ShowSection(s, helpDir);
		return;
		}

	// outsource all other URLs

	JString cmd;
	const JCharacter* varName = NULL;

	if (s.BeginsWith(kMailPrefix, kFalse))
		{
		cmd     = *itsSendMailCmd;
		varName = kMailAddressVarName;

		s.RemoveSubstring(1, kMailPrefixLength);
		}
	else
		{
		cmd     = *itsViewURLCmd;
		varName = kURLVarName;
		}

	// if we matched something, run the command

	if (!cmd.IsEmpty())
		{
		assert( varName != NULL );

		JSubstitute sub;
		sub.IgnoreUnrecognized();
		sub.DefineVariable(varName, s);
		sub.Substitute(&cmd);

		JSimpleProcess::Create(cmd, kTrue);
		}
}

/******************************************************************************
 Outsourced commands

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

void
JXHelpManager::SetViewURLCmd
	(
	const JCharacter* cmd
	)
{
	*itsViewURLCmd = cmd;
}

void
JXHelpManager::SetSendMailCmd
	(
	const JCharacter* cmd
	)
{
	*itsSendMailCmd = cmd;
}

/*****************************************************************************
 CloseAll

	Close all open help windows.

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

void
JXHelpManager::CloseAll()
{
	const JSize count = itsSections->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		SectionInfo info = itsSections->GetElement(i);
		if (info.dir != NULL)
			{
			(info.dir)->Close();
			// DirectorClosed() clears pointer
			}
		}
}

/******************************************************************************
 EditPrefs

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

void
JXHelpManager::EditPrefs()
{
	assert( itsPrefsDialog == NULL );

	itsPrefsDialog = new JXEditHelpPrefsDialog(this, *itsViewURLCmd, *itsSendMailCmd);
	assert( itsPrefsDialog != NULL );
	itsPrefsDialog->BeginDialog();
	ListenTo(itsPrefsDialog);
}

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

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

void
JXHelpManager::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (sender == itsPrefsDialog && message.Is(JXDialogDirector::kDeactivated))
		{
		const JXDialogDirector::Deactivated* info =
			dynamic_cast(const JXDialogDirector::Deactivated*, &message);
		assert( info != NULL );
		if (info->Successful())
			{
			itsPrefsDialog->GetPrefs(itsViewURLCmd, itsSendMailCmd);
			}
		itsPrefsDialog = NULL;
		}

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

/******************************************************************************
 SaveWindowPrefs

	The window will never be iconified because this is triggered by a menu
	item without a shortcut.

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

void
JXHelpManager::SaveWindowPrefs
	(
	JXWindow* window
	)
{
	window->WriteGeometry(itsDefWindowGeom);
}

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

	We assert that we can read the given data because there is no
	way to skip over it.

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

void
JXHelpManager::ReadSetup
	(
	istream& input
	)
{
	JFileVersion vers;
	input >> vers;
	assert( vers <= kCurrentSetupVersion );

	input >> *itsViewURLCmd >> *itsSendMailCmd;
	if (vers < 2)
		{
		ConvertVarNames(itsViewURLCmd,  kURLVarName);
		ConvertVarNames(itsSendMailCmd, kMailAddressVarName);
		}

	if (vers >= 1)
		{
		input >> *itsDefWindowGeom;
		}

	if (vers >= 3)
		{
		itsPrinter->ReadXPSSetup(input);
		}
}

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

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

void
JXHelpManager::WriteSetup
	(
	ostream& output
	)
	const
{
	output << kCurrentSetupVersion;

	output << ' ' << *itsViewURLCmd;
	output << ' ' << *itsSendMailCmd;
	output << ' ' << *itsDefWindowGeom;

	output << ' ';
	itsPrinter->WriteXPSSetup(output);
}

/******************************************************************************
 ConvertVarNames (static)

	Convert % to $ when followed by any character in varNameList.
	Backslashes and dollars are also backslashed, as required by JSubstitute.

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

void
JXHelpManager::ConvertVarNames
	(
	JString*			s,
	const JCharacter*	varNameList
	)
{
	// escape existing backslashes

	JIndex i = 1;
	while (s->LocateNextSubstring("\\", &i))
		{
		s->InsertSubstring("\\", i);
		i += 2;		// move past both backslashes
		}

	// escape existing dollars

	i = 1;
	while (s->LocateNextSubstring("$", &i))
		{
		s->InsertSubstring("\\", i);
		i += 2;		// move past $
		}

	// convert % to $ if followed by a variable name

	i = 1;
	while (s->LocateNextSubstring("%", &i) && i < s->GetLength())
		{
		const JCharacter c = s->GetCharacter(i+1);
		if (strchr(varNameList, c) != NULL)
			{
			s->SetCharacter(i, '$');
			}
		i += 2;		// move past variable name
		}
}

/*****************************************************************************
 DirectorClosed (virtual protected)

	Listen for help directors that are closed.

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

void
JXHelpManager::DirectorClosed
	(
	JXDirector* theDirector
	)
{
	const JSize count = itsSections->GetElementCount();
	for (JIndex i=1; i<=count; i++)
		{
		SectionInfo info = itsSections->GetElement(i);
		if (theDirector == info.dir)
			{
			info.dir = NULL;
			itsSections->SetElement(i, info);
			break;
			}
		}
}

/******************************************************************************
 CompareSections (static private)

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

JOrderedSetT::CompareResult
JXHelpManager::CompareSections
	(
	const SectionInfo& s1,
	const SectionInfo& s2
	)
{
	const int r = JStringCompare(s1.name, s2.name, kFalse);

	if (r > 0)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else if (r < 0)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else
		{
		return JOrderedSetT::kFirstEqualSecond;
		}
}

#define JTemplateType JXHelpManager::SectionInfo
#include <JArray.tmpls>
#undef JTemplateType
