/******************************************************************************
 JXHelpText.cc

	Displays hypertext for JXHelp system.

	BASE CLASS = JXLinkText

	Copyright © 1998 by John Lindal. All rights reserved.

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

#include <JXHelpText.h>
#include <JXHelpDirector.h>
#include <JXWindow.h>
#include <JXColormap.h>
#include <jXGlobals.h>
#include <jXKeysym.h>
#include <jStrStreamUtil.h>
#include <jASCIIConstants.h>
#include <ctype.h>
#include <jAssert.h>

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

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

JXHelpText::JXHelpText
	(
	const JCharacter*	text,
	JXScrollbarSet*		scrollbarSet,
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
	:
	JXLinkText(scrollbarSet, enclosure, hSizing, vSizing, x,y, w,h)
{
	itsMarks = new JArray<MarkInfo>;
	assert( itsMarks != NULL );
	itsMarks->SetCompareFunction(CompareMarkNames);

	itsLinks = new JArray<LinkInfo>;
	assert( itsLinks != NULL );

	itsAnchorText = NULL;

	// set text

	TESetLeftMarginWidth(kMinLeftMarginWidth);

	jistrstream(input, text, strlen(text));
	ReadHTML(input);
}

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

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

JXHelpText::~JXHelpText()
{
JIndex i;

	const JSize markCount = itsMarks->GetElementCount();
	for (i=1; i<=markCount; i++)
		{
		MarkInfo info = itsMarks->GetElement(i);
		delete info.name;
		}
	delete itsMarks;

	const JSize linkCount = itsLinks->GetElementCount();
	for (i=1; i<=linkCount; i++)
		{
		LinkInfo info = itsLinks->GetElement(i);
		delete info.url;
		}
	delete itsLinks;

	delete itsAnchorText;
}

/******************************************************************************
 ShowSubsection

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

void
JXHelpText::ShowSubsection
	(
	const JCharacter* name
	)
{
	if (JStringEmpty(name))
		{
		ScrollTo(0,0);
		}
	else
		{
		JString s = name;
		MarkInfo info(&s, 0);
		JIndex index;
		if (itsMarks->SearchSorted(info, JOrderedSetT::kAnyMatch, &index))
			{
			info = itsMarks->GetElement(index);
			ScrollTo(0, GetLineTop(GetLineForChar(info.index)));
			}
		}
}

/******************************************************************************
 GetLinkCount (virtual protected)

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

JSize
JXHelpText::GetLinkCount()
	const
{
	return itsLinks->GetElementCount();
}

/******************************************************************************
 GetLinkRange (virtual protected)

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

JIndexRange
JXHelpText::GetLinkRange
	(
	const JIndex index
	)
	const
{
	return (itsLinks->GetElement(index)).range;
}

/******************************************************************************
 LinkClicked (virtual protected)

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

void
JXHelpText::LinkClicked
	(
	const JIndex index
	)
{
	JXHelpDirector* helpDir =
		dynamic_cast(JXHelpDirector*, (GetWindow())->GetDirector());
	assert( helpDir != NULL );

	const LinkInfo info = itsLinks->GetElement(index);
	(JXGetHelpManager())->ShowURL(*(info.url), helpDir);
}

/******************************************************************************
 PrepareToReadHTML (virtual protected)

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

void
JXHelpText::PrepareToReadHTML()
{
	JXLinkText::PrepareToReadHTML();

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

	ClearAnchorInfo();
}

/******************************************************************************
 ReadHTMLFinished (virtual protected)

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

void
JXHelpText::ReadHTMLFinished()
{
JIndex i;

	JXLinkText::ReadHTMLFinished();

	delete itsAnchorText;
	itsAnchorText = NULL;

	// shift each mark to nearest non-blank line

	const JString& text    = GetText();
	const JSize textLength = GetTextLength();

	const JSize markCount = itsMarks->GetElementCount();
	for (i=1; i<=markCount; i++)
		{
		MarkInfo info = itsMarks->GetElement(i);
		while (info.index < textLength &&
			   text.GetCharacter(info.index) == '\n')
			{
			(info.index)++;
			}
		itsMarks->SetElement(i, info);
		}

	// highlight each link

	const JColorIndex blueColor = (GetColormap())->GetBlueColor();

	const JSize linkCount = itsLinks->GetElementCount();
	for (i=1; i<=linkCount; i++)
		{
		const LinkInfo info = itsLinks->GetElement(i);
		SetSelection(info.range);
		SetCurrentFontColor(blueColor);
		SetCurrentFontUnderline(1);
		}

	SetCaretLocation(1);
	ClearUndo();
}

/******************************************************************************
 HandleHTMLTag (virtual protected)

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

void
JXHelpText::HandleHTMLTag
	(
	const JString&					name,
	const JStringPtrMap<JString>&	attr,
	const JIndexRange&				range
	)
{
	if (name == "a")
		{
		BeginAnchor(attr);
		}
	else if (name == "/a" && itsAnchorRange.first > 0)
		{
		EndAnchor();
		}
	else
		{
		JXLinkText::HandleHTMLTag(name, attr, range);
		}
}

/******************************************************************************
 BeginAnchor (private)

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

void
JXHelpText::BeginAnchor
	(
	const JStringPtrMap<JString>& attr
	)
{
	JString* valueStr;
	if (attr.GetElement("href", &valueStr))
		{
		if (!itsAnchorText->IsEmpty())
			{
			EndAnchor();
			}

		itsAnchorRange.first = GetTextLength()+1;
		*itsAnchorText       = *valueStr;
		}
	else if (attr.GetElement("name", &valueStr))
		{
		MarkInfo info(new JString(*valueStr), GetTextLength()+1);
		assert( info.name != NULL );

		if (!itsMarks->InsertSorted(info, kFalse))
			{
			delete info.name;
			}
		}
}

/******************************************************************************
 EndAnchor (private)

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

void
JXHelpText::EndAnchor()
{
	assert( itsAnchorRange.first > 0 );

	itsAnchorRange.last = GetTextLength();

	const JString& text = GetText();
	while (itsAnchorRange.first <= itsAnchorRange.last &&
		   isspace(text.GetCharacter(itsAnchorRange.first)))
		{
		(itsAnchorRange.first)++;
		}
	while (itsAnchorRange.first <= itsAnchorRange.last &&
		   isspace(text.GetCharacter(itsAnchorRange.last)))
		{
		(itsAnchorRange.last)--;
		}

	if (!itsAnchorRange.IsEmpty() && !itsAnchorText->IsEmpty())
		{
		LinkInfo info(itsAnchorRange, new JString(*itsAnchorText));
		assert( info.url != NULL );
		itsLinks->AppendElement(info);
		}

	ClearAnchorInfo();
}

/******************************************************************************
 ClearAnchorInfo (private)

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

void
JXHelpText::ClearAnchorInfo()
{
	itsAnchorRange.SetToNothing();
	itsAnchorText->Clear();
}

/******************************************************************************
 CompareMarkNames (static private)

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

JOrderedSetT::CompareResult
JXHelpText::CompareMarkNames
	(
	const MarkInfo& m1,
	const MarkInfo& m2
	)
{
	return JCompareStringsCaseInsensitive(m1.name, m2.name);
}

#define JTemplateType JXHelpText::MarkInfo
#include <JArray.tmpls>
#undef JTemplateType

#define JTemplateType JXHelpText::LinkInfo
#include <JArray.tmpls>
#undef JTemplateType
