/******************************************************************************
 JXSRTextDialogBase.cc

	This is used as a global object.  We relay all the requests for action
	to the active text editor.  This way, all the text editors share the
	same dialog window.

	BASE CLASS = JXWindowDirector

	Copyright © 1996 by John Lindal. All rights reserved.

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

#include "JXSRTextDialogBase.h"
#include "JXSRDialogUpdateTask.h"
#include "JXSRTextBase.h"
#include "JXRegexHelpText.h"

#include <JXDisplay.h>
#include <JXWindow.h>
#include <JXStaticText.h>
#include <JXTextButton.h>
#include <JXInputField.h>
#include <JXTextCheckbox.h>
#include <JXRadioGroup.h>
#include <JXTextRadioButton.h>
#include <JXStringHistoryMenu.h>
#include <JXHelpManager.h>

#include <JRegex.h>
#include <JSubstitute.h>
#include <JInterpolate.h>

#include <jXGlobals.h>
#include <jAssert.h>

enum
{
	kSearchForward = 1,
	kSearchBackward
};

// setup information

const JFileVersion kCurrentSetupVersion = 3;

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

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

JXSRTextDialogBase::JXSRTextDialogBase
	(
	JXDirector* supervisor
	)
	:
	JXWindowDirector(supervisor),
	itsSearchRG(NULL),
    itsCloseButton(NULL),
    itsFindButton(NULL),
    itsIgnoreCaseCB(NULL),
    itsWrapSearchCB(NULL),
    itsReplaceButton(NULL),
    itsReplaceAllButton(NULL),
    itsReplaceFindButton(NULL),
    itsEntireWordCB(NULL),
    itsPrevReplaceMenu(NULL),
    itsPrevSearchMenu(NULL),
    itsSearchInput(NULL),
    itsReplaceInput(NULL),
    itsHelpButton(NULL),
    itsSearchIsRegexCB(NULL),
    itsReplaceIsRegexCB(NULL),
    itsSingleLineCB(NULL),
    itsPreserveCaseCB(NULL),
    itsQRefButton(NULL)
{
	itsActiveTextEditor = NULL;
	itsCanReplace		= kFalse;

	itsRegex = new JRegex;
	assert( itsRegex != NULL );
	itsRegex->SetDialect(JRegex::kExtended);	// egrep
	itsRegex->SetNoJExtended(kFalse);			// with perl extensions
	itsRegex->SetMatchOnly(kFalse);				// we want substring matches
	itsRegex->SetLineBegin(kTrue);				// ^ matches beginning of line, not text
	itsRegex->SetLineEnd(kTrue);				// $ matches end of line, not text
	(itsRegex->GetPatternEscapeEngine())->SetWhitespaceEscapes();
	(itsRegex->GetMatchInterpolator())->SetWhitespaceEscapes();

	itsUpdateTask = new JXSRDialogUpdateTask(this);
	assert( itsUpdateTask != NULL );
}

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

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

JXSRTextDialogBase::~JXSRTextDialogBase()
{
	delete itsUpdateTask;
	delete itsRegex;
}

/******************************************************************************
 Activate (virtual)

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

void
JXSRTextDialogBase::Activate()
{
	JXWindowDirector::Activate();

	if (IsActive())
		{
		itsSearchInput->Focus();
		itsSearchInput->SelectAll();

		(JXGetApplication())->InstallIdleTask(itsUpdateTask);
		}
}

/******************************************************************************
 Deactivate (virtual)

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

JBoolean
JXSRTextDialogBase::Deactivate()
{
	if (JXWindowDirector::Deactivate())
		{
		(JXGetApplication())->RemoveIdleTask(itsUpdateTask);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 SetSearchString

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

void
JXSRTextDialogBase::SetSearchString
	(
	const JCharacter* str
	)
{
	itsSearchInput->SetText(str);
	itsPrevSearchMenu->AddString(str);
	itsSearchIsRegexCB->SetState(kFalse);
}

/******************************************************************************
 SetReplaceString

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

void
JXSRTextDialogBase::SetReplaceString
	(
	const JCharacter* str
	)
{
	assert(itsCanReplace);
	itsReplaceInput->SetText(str);
	itsPrevReplaceMenu->AddString(str);
	itsReplaceIsRegexCB->SetState(kFalse);
}

/******************************************************************************
 SetSearchObjects

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

void
JXSRTextDialogBase::SetSearchObjects
	(
	JXRadioGroup* 			searchRG, 
	JXTextButton* 			closeButton,
    JXTextButton* 			findButton, 
    JXTextCheckbox* 		ignoreCaseCB,
    JXTextCheckbox* 		wrapSearchCB, 
    JXTextCheckbox* 		entireWordCB, 
    JXStringHistoryMenu*	prevSearchMenu,
    JXInputField* 			searchInput,
    JXTextButton* 			helpButton,
    JXTextCheckbox* 		searchIsRegexCB,
    JXTextCheckbox* 		singleLineCB,
    JXTextCheckbox* 		preserveCaseCB,
    JXTextButton* 			qRefButton,
    const JCharacter* 		regexHelpName,
	const JCharacter* 		qRefHelpName
	)
{
	assert(searchRG 		!= NULL);
	assert(closeButton	 	!= NULL);
	assert(findButton 		!= NULL);
	assert(ignoreCaseCB 	!= NULL);
	assert(wrapSearchCB 	!= NULL);
	assert(entireWordCB 	!= NULL);
	assert(prevSearchMenu 	!= NULL);
	assert(searchInput 		!= NULL);
	assert(helpButton 		!= NULL);
	assert(searchIsRegexCB	!= NULL);
	assert(singleLineCB 	!= NULL);
	assert(preserveCaseCB 	!= NULL);
	assert(qRefButton 		!= NULL);

    itsSearchRG 			= searchRG;
    itsCloseButton 			= closeButton;
    itsFindButton 			= findButton;
    itsIgnoreCaseCB 		= ignoreCaseCB;
    itsWrapSearchCB 		= wrapSearchCB;
    itsEntireWordCB 		= entireWordCB;
    itsPrevSearchMenu 		= prevSearchMenu;
    itsSearchInput 			= searchInput;
    itsHelpButton 			= helpButton;
    itsSearchIsRegexCB 		= searchIsRegexCB;
    itsSingleLineCB 		= singleLineCB;
    itsPreserveCaseCB 		= preserveCaseCB;
    itsQRefButton 			= qRefButton;

    itsRegexHelpName = regexHelpName;
    itsRegexQRefName = qRefHelpName;
	
	JXWindow* window = GetWindow();
	window->SetTitle("Search Text");
	window->SetCloseAction(JXWindow::kDeactivateDirector);
	window->PlaceAsDialogWindow();
	window->LockCurrentMinSize();
	window->SetFocusWhenShow(kTrue);
	ListenTo(window);

	ListenTo(itsFindButton);
	ListenTo(itsHelpButton);
	ListenTo(itsQRefButton);
	ListenTo(itsCloseButton);

	ListenTo(itsPrevSearchMenu);

	itsSearchRG->SelectItem(kSearchForward);
	itsIgnoreCaseCB->SetState(kTrue);
	itsWrapSearchCB->SetState(kTrue);
	itsEntireWordCB->SetState(kFalse);
	itsSearchIsRegexCB->SetState(kFalse);
	itsSingleLineCB->SetState(kFalse);
	itsPreserveCaseCB->SetState(kFalse);

	if (itsRegexHelpName.IsEmpty())
		{
		itsRegexHelpName = kRegexHelpName;
		JXGetHelpManager()->RegisterSection(kRegexHelpName, kRegexHelpTitle, 
											kRegexHelpText);
		}
	if (itsRegexQRefName.IsEmpty())
		{
		itsRegexQRefName = kRegexQRefName;
		JXGetHelpManager()->RegisterSection(kRegexQRefName, kRegexQRefTitle, 
											kRegexQRefText);
		}
}

/******************************************************************************
 SetReplaceObjects

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

void
JXSRTextDialogBase::SetReplaceObjects
	(
	JXTextButton* 			replaceButton,
    JXTextButton* 			replaceAllButton, 
    JXTextButton* 			replaceFindButton,
    JXStringHistoryMenu*	prevReplaceMenu,
    JXInputField* 			replaceInput,
    JXTextCheckbox* 		replaceIsRegexCB
	)
{
	assert(replaceButton 		!= NULL);
	assert(replaceAllButton		!= NULL);
	assert(replaceFindButton 	!= NULL);
	assert(prevReplaceMenu 		!= NULL);
	assert(replaceInput 		!= NULL);
	assert(replaceIsRegexCB 	!= NULL);

    itsReplaceButton 		= replaceButton;
    itsReplaceAllButton 	= replaceAllButton;
    itsReplaceFindButton	= replaceFindButton;
    itsPrevReplaceMenu 		= prevReplaceMenu;
    itsReplaceInput 		= replaceInput;
    itsReplaceIsRegexCB 	= replaceIsRegexCB;

	itsCanReplace = kTrue;
	
	ListenTo(itsReplaceButton);
	ListenTo(itsReplaceAllButton);
	ListenTo(itsReplaceFindButton);

	ListenTo(itsPrevReplaceMenu);

	itsReplaceIsRegexCB->SetState(kFalse);
}

/******************************************************************************
 SetActiveTextEditor

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

void
JXSRTextDialogBase::SetActiveTextEditor
	(
	JXSRTextBase* editor
	)
{
	itsActiveTextEditor = editor;
	UpdateDisplay();
}

/******************************************************************************
 GetActiveTextEditor

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

JBoolean
JXSRTextDialogBase::GetActiveTextEditor
	(
	JXSRTextBase** editor
	)
{
	if (itsActiveTextEditor == NULL)
		{
		return kFalse;
		}
	*editor = itsActiveTextEditor;
	return kTrue;
}

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

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

void
JXSRTextDialogBase::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	JXWindow* window = GetWindow();
	
	const JIndex searchDirection = itsSearchRG->GetSelectedItem();

	JXWindowDirector* textDir = NULL;
	JXSRTextBase* te       	  = itsActiveTextEditor;

	if (te != NULL)
		{
		textDir = te->GetWindow()->GetDirector();;
		}

	if (sender == itsFindButton && message.Is(JXButton::kPushed))
		{
		if (te != NULL &&
			((searchDirection == kSearchForward  && te->SRSearchForward()) ||
			 (searchDirection == kSearchBackward && te->SRSearchBackward())))
			{
			textDir->Activate();
			}
		}

	else if (sender == itsReplaceButton && message.Is(JXButton::kPushed))
		{
		if (te != NULL && te->ReplaceSelection())
			{
			textDir->Activate();
			}
		}

	else if (sender == itsReplaceFindButton && message.Is(JXButton::kPushed))
		{
		if (te != NULL &&
			((searchDirection == kSearchForward  && te->ReplaceAndSearchForward()) ||
			 (searchDirection == kSearchBackward && te->ReplaceAndSearchBackward())))
			{
			textDir->Activate();
			}
		}

	else if (sender == itsReplaceAllButton && message.Is(JXButton::kPushed))
		{
		if (te != NULL &&
			((searchDirection == kSearchForward  && te->ReplaceAllForward()) ||
			 (searchDirection == kSearchBackward && te->ReplaceAllBackward())))
			{
			textDir->Activate();
			}
		}

	else if (sender == itsHelpButton && message.Is(JXButton::kPushed))
		{
		if (!itsRegexHelpName.IsEmpty())
			{
			(JXGetHelpManager())->ShowSection(itsRegexHelpName);
			}
		}
	else if (sender == itsQRefButton && message.Is(JXButton::kPushed))
		{
		if (!itsRegexQRefName.IsEmpty())
			{
			(JXGetHelpManager())->ShowSection(itsRegexQRefName);
			}
		}

	else if (sender == itsCloseButton && message.Is(JXButton::kPushed))
		{
		Deactivate();
		}

	else if (sender == itsPrevSearchMenu && message.Is(JXMenu::kItemSelected))
		{
		const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		// need local copy since menu is changed
		const JString str       = itsPrevSearchMenu->GetItemText(selection->GetIndex());
		const JBoolean wasRegex = itsSearchIsRegexCB->IsChecked();
		SetSearchString(str);
		itsSearchIsRegexCB->SetState(wasRegex);
		}
	else if (sender == itsPrevReplaceMenu && message.Is(JXMenu::kItemSelected))
		{
		const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );
		// need local copy since menu is changed
		const JString str       = itsPrevReplaceMenu->GetItemText(selection->GetIndex());
		const JBoolean wasRegex = itsReplaceIsRegexCB->IsChecked();
		SetReplaceString(str);
		itsReplaceIsRegexCB->SetState(wasRegex);
		}

	else if (sender == window && message.Is(JXWindow::kIconified))
		{
		if (window->IsVisible())
			{
			(JXGetApplication())->RemoveIdleTask(itsUpdateTask);
			}
		}
	else if (sender == window && message.Is(JXWindow::kDeiconified))
		{
		if (window->IsVisible())
			{
			(JXGetApplication())->InstallIdleTask(itsUpdateTask);
			}
		}

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

/******************************************************************************
 HasSearchString

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

JBoolean
JXSRTextDialogBase::HasSearchString()
	const
{
	return JNegate(itsSearchInput->IsEmpty());
}

/******************************************************************************
 HasReplaceString

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

JBoolean
JXSRTextDialogBase::HasReplaceString()
	const
{
	assert(itsCanReplace);
	return JNegate(itsReplaceInput->IsEmpty());
}

/******************************************************************************
 GetSearchParameters

	Without regex, "entire word" is only possible if searchStr contains
	a single word.

	Returns kFalse if an error occurs.  (e.g. if regex compile fails)

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

JBoolean
JXSRTextDialogBase::GetSearchParameters
	(
	JString*	searchStr,
	JBoolean*	searchIsRegex,
	JBoolean*	caseSensitive,
	JBoolean*	entireWord,
	JBoolean*	wrapSearch,
	JBoolean*	preserveCase,
	JRegex**	regex
	)
	const
{
	*wrapSearch    = itsWrapSearchCB->IsChecked();
	*caseSensitive = JNegate( itsIgnoreCaseCB->IsChecked() );
	*preserveCase  = itsPreserveCaseCB->IsChecked();

	*searchStr     = itsSearchInput->GetText();
	*searchIsRegex = itsSearchIsRegexCB->IsChecked();
	if (*searchIsRegex)
		{
		itsRegex->SetCaseSensitive(*caseSensitive);
		itsRegex->SetSingleLine(itsSingleLineCB->IsChecked());
		const JError err = itsRegex->SetPattern(*searchStr);
		if (!err.OK())
			{
			itsSearchInput->Focus();
			err.ReportError();
			return kFalse;
			}
		}

	if (itsCanReplace)
		{
		JString replaceStr     	= itsReplaceInput->GetText();
		JBoolean replaceIsRegex = itsReplaceIsRegexCB->IsChecked();
		if (replaceIsRegex)
			{
			itsRegex->SetMatchCase(*preserveCase);
			JIndexRange errRange;
			const JError err = itsRegex->SetReplacePattern(replaceStr, &errRange);
			if (!err.OK())
				{
				itsReplaceInput->Focus();
				itsReplaceInput->SetSelection(errRange);
				err.ReportError();
				return kFalse;
				}
			}
		}

	if (*searchIsRegex ||
		itsSearchInput->IsEntireWord(1, itsSearchInput->GetTextLength()))
		{
		*entireWord = itsEntireWordCB->IsChecked();
		}
	else
		{
		*entireWord = kFalse;
		}
	

	*regex = itsRegex;

	// use the original strings, not the substituted ones

	itsPrevSearchMenu->AddString(itsSearchInput->GetText());

	if (itsCanReplace)
		{
		itsPrevReplaceMenu->AddString(itsReplaceInput->GetText());
		}

	return kTrue;
}

/******************************************************************************
 GetReplaceParameters

	Without regex, "entire word" is only possible if searchStr contains
	a single word.

	Returns kFalse if an error occurs.  (e.g. if regex compile fails)

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

JBoolean
JXSRTextDialogBase::GetReplaceParameters
	(
	JString*	replaceStr,
	JBoolean*	replaceIsRegex
	)
	const
{
	assert(itsCanReplace);
	*replaceStr     = itsReplaceInput->GetText();
	*replaceIsRegex = itsReplaceIsRegexCB->IsChecked();

	// use the original strings, not the substituted ones

	itsPrevSearchMenu->AddString(itsSearchInput->GetText());
	itsPrevReplaceMenu->AddString(itsReplaceInput->GetText());

	return kTrue;
}

/******************************************************************************
 UpdateDisplay (private)

	We never need to call this because our update task calls it regularly.

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

void
JXSRTextDialogBase::UpdateDisplay()
{
	const JBoolean hasSearchString = HasSearchString();
	const JBoolean teWritable =
		JConvertToBoolean( itsActiveTextEditor != NULL && 
				itsActiveTextEditor->GetType() == JTextEditor::kFullEditor );

	if (itsActiveTextEditor != NULL && hasSearchString)
		{
		itsFindButton->Activate();
		}
	else
		{
		itsFindButton->Deactivate();
		}

	if (itsCanReplace)
		{
		if (itsActiveTextEditor != NULL && teWritable && 
			itsActiveTextEditor->HasSelection())
			{
			itsReplaceButton->Activate();
			}
		else
			{
			itsReplaceButton->Deactivate();
			}

		if (itsActiveTextEditor != NULL && hasSearchString && teWritable && 
			itsActiveTextEditor->HasSelection())
			{
			itsReplaceFindButton->Activate();
			}
		else
			{
			itsReplaceFindButton->Deactivate();
			}

		if (itsActiveTextEditor != NULL && hasSearchString && teWritable)
			{
			itsReplaceAllButton->Activate();
			}
		else
			{
			itsReplaceAllButton->Deactivate();
			}
		}

	if (itsSearchIsRegexCB->IsChecked())
		{
		itsSingleLineCB->Activate();
		}
	else
		{
		itsSingleLineCB->Deactivate();
		}
}

/******************************************************************************
 SetFont

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

void
JXSRTextDialogBase::SetFont
	(
	const JCharacter*	name,
	const JSize			size
	)
{
	itsSearchInput->SetFont(name, size);

	if (itsCanReplace)
		{
		itsReplaceInput->SetFont(name, size);
		}
}

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

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

void
JXSRTextDialogBase::ReadSetup
	(
	istream& input
	)
{
	JFileVersion vers;
	input >> vers;
	if (vers > kCurrentSetupVersion)
		{
		return;
		}

	JXWindow* window = GetWindow();
	window->ReadGeometry(input);
	window->Deiconify();

	JIndex id;
	input >> id;
	itsSearchRG->SelectItem(id);

	JBoolean wrapSearch, ignoreCase, entireWord, searchIsRegex;
	input >> wrapSearch >> ignoreCase >> entireWord >> searchIsRegex;
	itsWrapSearchCB->SetState(wrapSearch);
	itsIgnoreCaseCB->SetState(ignoreCase);
	itsEntireWordCB->SetState(entireWord);
	itsSearchIsRegexCB->SetState(searchIsRegex);


	JBoolean singleLine, matchCase;
	input >> singleLine >> matchCase;
	itsSingleLineCB->SetState(singleLine);
	itsPreserveCaseCB->SetState(matchCase);

	itsPrevSearchMenu->ReadSetup(input);

	if (itsCanReplace)
		{
		JBoolean replaceIsRegex;
		input >> replaceIsRegex;
		itsReplaceIsRegexCB->SetState(replaceIsRegex);
		itsPrevReplaceMenu->ReadSetup(input);
		}

}

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

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

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

	output << ' ';
	(GetWindow())->WriteGeometry(output);

	output << ' ' << itsSearchRG->GetSelectedItem();
	output << ' ' << itsWrapSearchCB->IsChecked();
	output << ' ' << itsIgnoreCaseCB->IsChecked();
	output << ' ' << itsEntireWordCB->IsChecked();
	output << ' ' << itsSearchIsRegexCB->IsChecked();
	output << ' ' << itsSingleLineCB->IsChecked();
	output << ' ' << itsPreserveCaseCB->IsChecked();

	output << ' ';
	itsPrevSearchMenu->WriteSetup(output);

	if (itsCanReplace)
		{
		output << ' ' << itsReplaceIsRegexCB->IsChecked();
		output << ' ';
		itsPrevReplaceMenu->WriteSetup(output);
		}
}

/******************************************************************************
 CanReplace

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

JBoolean
JXSRTextDialogBase::CanReplace()
{
	return itsCanReplace;
}
