/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- February 1998
 */

/*
 * CommitDlg.cpp : the cvs commit dialog
 */

#include "stdafx.h"

#ifdef WIN32
#	include "wincvs.h"
#	include <process.h>
#else /* !WIN32 */
#	include <ctype.h>
#endif /* !WIN32 */

#if qUnix
#	include "UCvsDialogs.h"
#endif

#ifdef qMacCvsPP
#	include <UModalDialogs.h>
#	include <LMultiPanelView.h>
#	include <LPopupGroupBox.h>
#	include <LCheckBox.h>
#	include <LTextEditView.h>
#	include <LPushButton.h>
#	include "LPopupFiller.h"

#	include "MacCvsConstant.h"
#endif /* qMacCvsPP */

#include "CommitDlg.h"
#include "CvsPrefs.h"
#include "MultiFiles.h"
#include "getline.h"
#include "MultiString.h"
#include "CvsCommands.h"
#include "EditorMonDlg.h"
#include "ItemListDlg.h"
#include "BrowseViewHandlers.h"

static CMString gCommitLogs(10, "P_CommitLogs");
static CPersistentBool gCheckValidEdits("P_CheckValidEdits", false);

/*!
	Show the ellipsis if the string is too long
	\param str String to modify
*/
void combozify(CStr& str)
{
	// extract the first xxx words
	const char* tmp = str;
	CStr result;
	int numWords = 0;
#	define HOWMUCH_WORDS 5
	while(*tmp != '\0' && numWords < HOWMUCH_WORDS)
	{
		while(*tmp != '\0' && isspace(*tmp))
			tmp++;

		while(*tmp != '\0' && !isspace(*tmp))
			result << *tmp ++;

		if( *tmp != '\0' )
			result << ' ';

		numWords++;
	}

	if( *tmp != '\0' )
	{
		result << "...";
	}

	str = result;
}

#ifdef WIN32
#	include "GetPrefs.h"
#	ifdef _DEBUG
#		define new DEBUG_NEW
#		undef THIS_FILE
		static char THIS_FILE[] = __FILE__;
#	endif

IMPLEMENT_DYNAMIC(CCommit_MAIN, CPropertyPage)

CCommit_MAIN::CCommit_MAIN(const MultiFiles *mf, const char *logMsg, const char *pathTmpl) 
	: m_mf(mf), m_fileToDiffCombo(USmartCombo::AutoDropWidth | USmartCombo::ReadOnly), 
	CPropertyPage(CCommit_MAIN::IDD)
{
	//{{AFX_DATA_INIT(CCommit_MAIN)
	m_logMsg = logMsg;
	m_wordWrap = FALSE;
	//}}AFX_DATA_INIT
	
	m_tmplPath = pathTmpl;
	if( !m_tmplPath.empty() )
	{
		if( !m_tmplPath.endsWith(kPathDelimiter) )
			m_tmplPath << kPathDelimiter;
		
		m_tmplPath << "CVS";
		m_tmplPath << kPathDelimiter;
		m_tmplPath << "Template";
	}
	
	m_bSwitchingWrap = FALSE;
	m_wordWrap = gCvsPrefs.WordWrapLogMsg();

	m_prevLogsCombo.SetItems(&gCommitLogs);
}

CCommit_MAIN::~CCommit_MAIN()
{
}

void CCommit_MAIN::DoDataExchange(CDataExchange* pDX)
{
	CPropertyPage::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CCommit_MAIN)
	DDX_Control(pDX, IDC_PREVIEWINFO, m_previewInfo);
	DDX_Control(pDX, IDC_FILETODIFF, m_fileToDiffCombo);
	DDX_Control(pDX, IDC_WORDWRAP, m_wordWrapCtrl);
	DDX_Control(pDX, IDC_LOGMSG_WRAP, m_logMsgCtrlWrap);
	DDX_Control(pDX, IDC_LOGMSG, m_logMsgCtrl);
	DDX_Control(pDX, IDC_COMBO, m_prevLogsCombo);
	DDX_Check(pDX, IDC_WORDWRAP, m_wordWrap);
	//}}AFX_DATA_MAP

	if( !pDX->m_bSaveAndValidate )
	{
		DDX_Text(pDX, IDC_LOGMSG_WRAP, m_logMsg);
		DDX_Text(pDX, IDC_LOGMSG, m_logMsg);
	}
	else
	{
		DDX_Text(pDX, GetLogMsgID(), m_logMsg);
	}

	if( pDX->m_bSaveAndValidate )
	{
		if( !m_logMsg.IsEmpty() )
		{
			gCommitLogs.Insert(m_logMsg);
		}
	}
	else
	{
		DDX_ComboMString(pDX, IDC_COMBO, m_prevLogsCombo);
	}
}

/// CBN_SELCHANGE message handler, copy the selected text as the log message
void CCommit_MAIN::OnSelchangeCombo() 
{
	int sel = m_prevLogsCombo.GetCurSel();
	if( sel == CB_ERR )
		return;

	CEdit* pEdit = (CEdit*)GetLogMsgWnd();
	pEdit->SetSel(0, -1);

	const NAMESPACE(std) vector<CStr>& list = gCommitLogs.GetList();
	pEdit->ReplaceSel(list[sel]);
}

BEGIN_MESSAGE_MAP(CCommit_MAIN, CPropertyPage)
	//{{AFX_MSG_MAP(CCommit_MAIN)
	ON_CBN_SELCHANGE(IDC_COMBO, OnSelchangeCombo)
	ON_BN_CLICKED(IDC_LAUNCHEDITOR, OnLauncheditor)
	ON_WM_ERASEBKGND()
	ON_BN_CLICKED(IDC_WORDWRAP, OnWordwrap)
	ON_BN_CLICKED(IDC_DIFF, OnDiff)
	ON_CBN_SELCHANGE(IDC_FILETODIFF, OnSelchangeFiletodiff)
	ON_BN_CLICKED(IDC_TEMPLATE, OnTemplate)
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/// PreTranslateMessage virtual override, update log message info and allow Esc key to close the dialog
BOOL CCommit_MAIN::PreTranslateMessage(MSG* pMsg) 
{
	if( pMsg->message >= WM_KEYFIRST ||
		pMsg->message <= WM_KEYLAST ||
		pMsg->message == WM_COPY || pMsg->message == WM_CUT || pMsg->message == WM_PASTE )
	{
		UpdateInfo();
	}

	//dialog can be dismissed by Esc key
	if( pMsg->message == WM_KEYDOWN && 
		(int)pMsg->wParam == VK_ESCAPE )
	{
		EndDialog(IDCANCEL);
	}
	
	return CPropertyPage::PreTranslateMessage(pMsg);
}

///	Display log message info
void CCommit_MAIN::UpdateInfo()
{
	CEdit* pEdit = (CEdit*)GetLogMsgWnd();
	
	CString strOldInfo;
	CString strInfo;
	int nLineNumber = pEdit->LineFromChar();
	int nLineLenght = pEdit->LineLength(pEdit->LineIndex());
	
	strInfo.Format("Line %d (%d chars)", nLineNumber + 1, nLineLenght);
	
	GetDlgItemText(IDC_EDIT_POSITION, strOldInfo);
	if( strOldInfo != strInfo )
	{
		SetDlgItemText(IDC_EDIT_POSITION, strInfo);
	}
}

/// Display preview file info
void CCommit_MAIN::UpdatePreviewInfo()
{
	CString strPreviewInfo;
	int nSelInfo = m_fileToDiffCombo.GetCurSel();
	if( nSelInfo < 0 || !m_fileToDiffCombo.GetCount() )
	{
		nSelInfo = 0;
	}
	else
	{
		nSelInfo++;
	}

	strPreviewInfo.Format("Preview file changes (%d/%d):", nSelInfo, m_fileToDiffCombo.GetCount());
	m_previewInfo.SetWindowText(strPreviewInfo);
}

/// CBN_SELCHANGE message handler, update preview file info
void CCommit_MAIN::OnSelchangeFiletodiff() 
{
	UpdatePreviewInfo();
}

/// BN_CLICKED message handler, launch external editor to enter the log message
void CCommit_MAIN::OnLauncheditor() 
{
	UStr message;
	
	if( CompatGetEditorMsg(message) )
	{
		CString strMessage(message);
		strMessage.Replace("\n", "\r\n");

		if( !strMessage.IsEmpty() )
		{
			CEdit* pEdit = (CEdit*)GetLogMsgWnd();
			pEdit->SetSel(0, -1);
			pEdit->ReplaceSel(strMessage);		
		}
	}

	GotoDlgCtrl(GetLogMsgWnd());
}

/// Switch the edit wrap and non-wrap edit boxes
void CCommit_MAIN::SwitchWrapEdit(BOOL bWordWrap)
{
	CString strMessage;

	if( bWordWrap )
	{
		m_logMsgCtrl.GetWindowText(strMessage);
		m_logMsgCtrlWrap.SetWindowText(strMessage);

		m_bSwitchingWrap = TRUE;
		m_logMsgCtrl.ShowWindow(SW_HIDE);
		m_logMsgCtrlWrap.ShowWindow(SW_SHOW);
		m_bSwitchingWrap = FALSE;
	}
	else
	{
		m_logMsgCtrlWrap.GetWindowText(strMessage);
		m_logMsgCtrl.SetWindowText(strMessage);

		m_bSwitchingWrap = TRUE;
		m_logMsgCtrlWrap.ShowWindow(SW_HIDE);
		m_logMsgCtrl.ShowWindow(SW_SHOW);
		m_bSwitchingWrap = FALSE;
	}
}

/// Get the log window dialog ID
int CCommit_MAIN::GetLogMsgID()
{
	return m_wordWrapCtrl.GetCheck() ? IDC_LOGMSG_WRAP : IDC_LOGMSG;
}

/// Get the log window
CWnd* CCommit_MAIN::GetLogMsgWnd()
{
	return GetDlgItem(GetLogMsgID());
}

/// WM_ERASEBKGND message handler, prevent flickering while switching the log message windows
BOOL CCommit_MAIN::OnEraseBkgnd(CDC* pDC) 
{
	if( m_bSwitchingWrap )
	{
		return TRUE;
	}
	
	return CPropertyPage::OnEraseBkgnd(pDC);
}

/// BN_CLICKED message handler, switch the log message windows 
void CCommit_MAIN::OnWordwrap() 
{
	gCvsPrefs.SetWordWrapLogMsg(m_wordWrapCtrl.GetCheck());
	SwitchWrapEdit(gCvsPrefs.WordWrapLogMsg());
}

/// BN_CLICKED message handler, launch external diff to preview the changes
void CCommit_MAIN::OnDiff() 
{
	const int nComboIndex = m_fileToDiffCombo.GetCurSel();
	if( nComboIndex > CB_ERR )
	{
		MultiFilesEntryIndex* entryIndex = (MultiFilesEntryIndex*)m_fileToDiffCombo.GetItemData(nComboIndex);
		CStr path;
		CStr fileName;
		CStr currRev;

		if( entryIndex && entryIndex->GetIterator()->get(entryIndex->GetIndex(), path, fileName, currRev) )
		{
			MultiFiles mf;
			mf.newdir(path);
			mf.newfile(fileName, 0, currRev);
			
			KoDiffHandler handler;
			handler.OnFiles(&mf);
		}
	}
}

/// OnInitDialog virtual override, fill the preview files
BOOL CCommit_MAIN::OnInitDialog() 
{
	CPropertyPage::OnInitDialog();
	
	// Extra initialization here
	SwitchWrapEdit(gCvsPrefs.WordWrapLogMsg());

	if( m_mf && m_mf->TotalNumFiles() )
	{
		MultiFiles::const_iterator mfi;
		for(mfi = m_mf->begin(); mfi != m_mf->end(); ++mfi)
		{
			for( int nIndex = 0; nIndex < mfi->NumFiles(); nIndex++ )
			{
				UStr path;
				UStr fileName;
				UStr currRev;

				mfi->get(nIndex, path, fileName, currRev);
				if( !path.empty() && !path.endsWith(kPathDelimiter) )
					path << kPathDelimiter;

				path << fileName;

				const int nComboIndex = m_fileToDiffCombo.AddString(path);
				
				MultiFilesEntryIndex* entryIndex = new MultiFilesEntryIndex(nComboIndex, mfi);
				m_fileToDiffCombo.SetItemData(nComboIndex, (DWORD)entryIndex);
			}
		}
	}

	if( m_fileToDiffCombo.GetCount() ) 
	{
		m_fileToDiffCombo.SetCurSel(0);
	}
	else
	{
		m_fileToDiffCombo.EnableWindow(FALSE);
		CWnd* pButton = GetDlgItem(IDC_DIFF);
		if( pButton )
		{
			pButton->EnableWindow(FALSE);
		}
	}
	
	//enable template button
	{
		CWnd* pTemplateButton = GetDlgItem(IDC_TEMPLATE);
		if( pTemplateButton )
		{
			FILE* templateFile = fopen(m_tmplPath, "r");
			pTemplateButton->EnableWindow(NULL != templateFile);
			if( templateFile )
			{
				fclose(templateFile);
			}
		}
	}

	UpdatePreviewInfo();

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

/// WM_DESTROY message handler, release allocated memory
void CCommit_MAIN::OnDestroy() 
{
	const int count = m_fileToDiffCombo.GetCount();
	for(int nIndex = 0; nIndex < count; nIndex++)
	{
		MultiFilesEntryIndex* entryIndex = (MultiFilesEntryIndex*)m_fileToDiffCombo.GetItemData(nIndex);
		delete entryIndex;
	}

	CPropertyPage::OnDestroy();
}

/// BN_CLICKED message handler, copy the message template as a message log
void CCommit_MAIN::OnTemplate() 
{
	CEdit* pEdit = (CEdit*)GetLogMsgWnd();
	pEdit->SetSel(0, -1);

	bool gotIt = false;
	FILE* output;
	
	if( !m_tmplPath.empty() && (output = fopen(m_tmplPath, "r")) != 0L )
	{
		char* line = 0L;
		size_t line_chars_allocated = 0;
		int line_length;
		while( (line_length = getline(&line, &line_chars_allocated, output)) > 0 )
		{
			//correct the new line characters for edit box and insert the line
			CString strLine(line);
			strLine.Replace( "\n", "\r\n" );
			pEdit->ReplaceSel(strLine);
		}
		
		gotIt = true;
		if( line_length < 0 && !feof(output) )
			gotIt = false;
		
		if( line != 0L )
			free(line);
		
		fclose(output);
	}
	
	if( !gotIt )
	{
		pEdit->ReplaceSel("*CVS/Template file not found*");
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCommit_OPTIONS property page

IMPLEMENT_DYNAMIC(CCommit_OPTIONS, CPropertyPage)

CCommit_OPTIONS::CCommit_OPTIONS(const MultiFiles* mf) 
	: m_mf(mf), CPropertyPage(CCommit_OPTIONS::IDD)
{
	//{{AFX_DATA_INIT(CCommit_OPTIONS)
	m_forceCommit = FALSE;
	m_rev = _T("");
	m_forceRecurse = FALSE;
	m_forceRevision = FALSE;
	m_noModuleProgram = FALSE;
	m_noRecurse = FALSE;
	m_checkValidEdits = FALSE;
	//}}AFX_DATA_INIT

	m_checkValidEdits = (bool)gCheckValidEdits;
	m_revCombo.SetItems(&gRevNames);
}

CCommit_OPTIONS::~CCommit_OPTIONS()
{
}

void CCommit_OPTIONS::DoDataExchange(CDataExchange* pDX)
{
	CPropertyPage::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CCommit_OPTIONS)
	DDX_Control(pDX, IDC_COMBOREV, m_revCombo);
	DDX_Check(pDX, IDC_FORCECOMMIT, m_forceCommit);
	DDX_CBString(pDX, IDC_COMBOREV, m_rev);
	DDX_Check(pDX, IDC_FORCERECURSE, m_forceRecurse);
	DDX_Check(pDX, IDC_FORCEREVISION, m_forceRevision);
	DDX_Check(pDX, IDC_NOMODULEPROGRAM, m_noModuleProgram);
	DDX_Check(pDX, IDC_NORECURS, m_noRecurse);
	DDX_Check(pDX, IDC_CHECKVALIDEDITS, m_checkValidEdits);
	//}}AFX_DATA_MAP

	if( m_forceRevision )
	{
		DDV_MinChars(pDX, m_rev, 1);
	}
	
	if( !pDX->m_bSaveAndValidate )
	{
		OnForcerevision();
	}
	
	DDX_ComboMString(pDX, IDC_COMBOREV, m_revCombo);
}


BEGIN_MESSAGE_MAP(CCommit_OPTIONS, CPropertyPage)
//{{AFX_MSG_MAP(CCommit_OPTIONS)
	ON_BN_CLICKED(IDC_FORCEREVISION, OnForcerevision)
	ON_BN_CLICKED(IDC_BROWSE_TAG, OnBrowseTag)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCommit_OPTIONS message handlers

/// BN_CLICKED message handler, enable controls for force revision option
void CCommit_OPTIONS::OnForcerevision() 
{
	CButton* but = (CButton*)GetDlgItem(IDC_FORCEREVISION);

	CWnd* wnd = GetDlgItem(IDC_COMBOREV);
	if( wnd )
	{
		wnd->EnableWindow(but->GetCheck());
	}

	wnd = GetDlgItem(IDC_BROWSE_TAG);
	if( wnd )
	{
		wnd->EnableWindow(but->GetCheck());
	}
}

/// BN_CLICKED message handler, display browse tag/branch dialog
void CCommit_OPTIONS::OnBrowseTag() 
{
	CStr tagName;
	if( CompatGetTagListItem(m_mf, tagName) )
	{
		m_rev = (const char*)tagName;
		m_revCombo.SetWindowText(m_rev);
	}
}

#endif /* WIN32 */

#ifdef qMacCvsPP
class LLogFillerWrapper : public LFillerWrapper
{
public:
	LLogFillerWrapper(LMenuController *menu, CMString & mstr, const char *pathTmpl) :
		LFillerWrapper(menu), fMStr(mstr), fFirstTime(true)
	{
		fTmplPath = pathTmpl;
		if(!fTmplPath.empty())
		{
			if(!fTmplPath.endsWith(kPathDelimiter))
				fTmplPath << kPathDelimiter;
			fTmplPath << "CVS";
			fTmplPath << kPathDelimiter;
			fTmplPath << "Template";
		}

	}
	
	virtual void OnAddingItem(CStr & str)
	{
		if(fFirstTime)
		{
			fMenu->InsertMenuItem("\pInsert CVS/Template", max_Int16);
			mTmplItem = ::CountMenuItems(fMenu->GetMacMenuH());
			fMenu->InsertMenuItem("\p(-", max_Int16, false);
			fFirstTime = false;
		}
		
		combozify(str);
		CPStr pstr(str);
		fMenu->InsertMenuItem(pstr, max_Int16);
	}
	
	virtual bool OnSelectingItem(SInt32 inItem, CStr & str)
	{
		str = "";
		if(inItem == mTmplItem)
		{
			bool gotIt = false;
			FILE *output;
			if(!fTmplPath.empty() && (output = fopen(fTmplPath, "r")) != 0L)
			{
				char *line = 0L;
				size_t line_chars_allocated = 0;
				int line_length;
				while ((line_length = getline (&line, &line_chars_allocated, output)) > 0)
				{
					str << line;
				}

				gotIt = true;
				if (line_length < 0 && !feof (output))
					gotIt = false;

				if(line != 0L)
					free (line);

				fclose(output);
			}
			if(!gotIt)
				str << "*found no CVS/Template file*";
			return true;
		}
		else if(inItem >= mTmplItem + 2)
		{
			str = fMStr.GetList()[inItem - 4];
			return true;
		}
		
		return false;
	}
protected:
	CMString & fMStr;
	CStr fTmplPath;
	SInt32 mTmplItem;
	bool fFirstTime;
};

static void DoDataExchange_Commit(LWindow *theDialog, CStr & commit,
	const char *pathTmpl, bool putValue)
{
	LTextEditView *editLog = dynamic_cast<LTextEditView*>
		(theDialog->FindPaneByID(item_LogMsg));
	LPopupFiller *fillLog = dynamic_cast<LPopupFiller*>
		(theDialog->FindPaneByID(item_FillerRev1));
	LLogFillerWrapper *wrapper = putValue ? NEW LLogFillerWrapper(fillLog, gCommitLogs, pathTmpl) : 0L;
	
	fillLog->DoDataExchange(gCommitLogs, putValue, wrapper);
	
	if(putValue)
	{
		editLog->SetTextPtr((Ptr)(const char *)commit, commit.length());	
		theDialog->SetLatentSub(editLog);
	}
	else
	{
		Handle hdl = editLog->GetTextHandle();
		Assert_(hdl != 0L);
		commit.set(*hdl, GetHandleSize(hdl));
		
	}
}

static void 
DoDataExchange_CommitOptions(
	LView *theDialog, 
	const MultiFiles* mf,
	bool & norecurs,
	bool & forceCommit,
	bool & forceRecurse,
	bool & hasRev,
	CPStr & lastRev, 
	bool & noModuleProgram,
	bool putValue)
{
	LPopupFiller *fillTag = dynamic_cast<LPopupFiller*> (theDialog->FindPaneByID(item_FillerTag));
	LTagWrapper *wrapper = putValue ? NEW LTagWrapper(fillTag, mf) : 0L;
	fillTag->DoDataExchange(gRevNames, putValue, wrapper);

	DoDataExchange (theDialog, item_NoRecurs, norecurs, putValue);
	DoDataExchange (theDialog, item_ForceCommit, forceCommit, putValue);
	DoDataExchange (theDialog, item_ForceRecurse, forceRecurse, putValue);
	DoDataExchange (theDialog, item_NoModuleProgram, noModuleProgram, putValue);
	DoDataExchange (theDialog, item_CheckRevTag, hasRev, putValue);
	DoDataExchange (theDialog, item_EditRevTag, lastRev, putValue);
}
#endif /* qMacCvsPP */

#if qUnix
class UCvsCommit : public UWidget
{
	UDECLARE_DYNAMIC(UCvsCommit)
public:
	UCvsCommit() : UWidget(::UEventGetWidID()) {}
	virtual ~UCvsCommit() {}

	enum
	{
		kOK = EV_COMMAND_START,	// 0
		kCancel,				// 1
		kTabGeneral             // 2
	};

	virtual void DoDataExchange(bool fill);

protected:
	ev_msg int OnOK(void);
	ev_msg int OnCancel(void);

	UDECLARE_MESSAGE_MAP()
};

UIMPLEMENT_DYNAMIC(UCvsCommit, UWidget)

UBEGIN_MESSAGE_MAP(UCvsCommit, UWidget)
	ON_UCOMMAND(UCvsCommit::kOK, UCvsCommit::OnOK)
	ON_UCOMMAND(UCvsCommit::kCancel, UCvsCommit::OnCancel)
UEND_MESSAGE_MAP()

int UCvsCommit::OnOK(void)
{
	EndModal(true);
	return 0;
}

int UCvsCommit::OnCancel(void)
{
	EndModal(false);
	return 0;
}

void UCvsCommit::DoDataExchange(bool fill)
{
	if(fill)
	{
	}
	else
	{
	}
}

class UCvsCommit_MAIN : public UWidget
{
	UDECLARE_DYNAMIC(UCvsCommit_MAIN)
public:
	UCvsCommit_MAIN( const MultiFiles *mf, const char *logmsg, const char *pathTmpl);
	virtual ~UCvsCommit_MAIN() {}

	enum
	{
		kEditLog = EV_COMMAND_START,	// 0
		kComboLog,				// 1
		kComboDiffSelection,     // 2
		kExecDiff                // 3
	};

	virtual void DoDataExchange(bool fill);

	UStr m_logmsg;
	UStr m_tmplPath;

protected:

	ev_msg void OnComboSel(int pos, const char *txt);
	ev_msg void OnComboDiffSel(int pos, const char *txt);
	ev_msg int OnExecDiff(void);

	UDECLARE_MESSAGE_MAP()
private:
	const MultiFiles* m_mf;
	int m_selected_diff_file;
};

UIMPLEMENT_DYNAMIC(UCvsCommit_MAIN, UWidget)

UBEGIN_MESSAGE_MAP(UCvsCommit_MAIN, UWidget)
	ON_COMBO_SEL(UCvsCommit_MAIN::kComboLog, UCvsCommit_MAIN::OnComboSel)
	ON_COMBO_SEL(UCvsCommit_MAIN::kComboDiffSelection, UCvsCommit_MAIN::OnComboDiffSel)
	ON_UCOMMAND(UCvsCommit_MAIN::kExecDiff, UCvsCommit_MAIN::OnExecDiff)
UEND_MESSAGE_MAP()

UCvsCommit_MAIN::UCvsCommit_MAIN( const MultiFiles *mf, const char *logmsg, const char *pathTmpl)
	: m_mf(mf), m_selected_diff_file(0), UWidget(::UEventGetWidID())
{
	m_logmsg = logmsg;
	m_tmplPath = pathTmpl;
	if(!m_tmplPath.empty())
	{
		if(!m_tmplPath.endsWith(kPathDelimiter))
			m_tmplPath << kPathDelimiter;
		m_tmplPath << "CVS";
		m_tmplPath << kPathDelimiter;
		m_tmplPath << "Template";
	}
}

void UCvsCommit_MAIN::OnComboSel(int pos, const char *txt)
{
	if(pos == 0)
	{
		bool gotIt = false;
		FILE *output;

		if(!m_tmplPath.empty() && (output = fopen(m_tmplPath, "r")) != 0L)
		{
			char *line = 0L;
			size_t line_chars_allocated = 0;
			int line_length;
			while ((line_length = getline (&line, &line_chars_allocated, output)) > 0)
			{
				UEventSendMessage(GetWidID(), EV_SETTEXT, kEditLog, line);
			}

			gotIt = true;
			if (line_length < 0 && !feof (output))
				gotIt = false;

			if(line != 0L)
				free (line);

			fclose(output);
		}
		if(!gotIt)
			UEventSendMessage(GetWidID(), EV_SETTEXT, kEditLog, (void *)"*found no CVS/Template file*");
	}
	else
	{
		const std::vector<CStr> & list = gCommitLogs.GetList();
		UEventSendMessage(GetWidID(), EV_SETTEXT, kEditLog, (void *)(const char *)list[pos - 1]);
	}
}

void UCvsCommit_MAIN::OnComboDiffSel(int pos, const char *txt)
{
	m_selected_diff_file = pos;
	printf( "selected file %d, '%s'\n", pos, txt);
}

void UCvsCommit_MAIN::DoDataExchange(bool fill)
{
	if(fill)
	{
		UEventSendMessage(GetWidID(), EV_SETTEXT, kEditLog, (void *)(const char *)m_logmsg);

		UEventSendMessage(GetWidID(), EV_COMBO_RESETALL, kComboLog, 0L);
		UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kComboLog,
						  (void *)"Insert CVS/Template");
		const std::vector<CStr> & list = gCommitLogs.GetList();
		std::vector<CStr>::const_iterator i;
		for(i = list.begin(); i != list.end(); ++i)
		{
			CStr str = *i;
			combozify(str);
			UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kComboLog, (void *)(const char *)str);
		}

		UEventSendMessage(GetWidID(), EV_COMBO_RESETALL, kComboDiffSelection, 0L);
		if( m_mf && m_mf->TotalNumFiles() )
		{
			MultiFiles::const_iterator mfi;
			for(mfi = m_mf->begin(); mfi != m_mf->end(); ++mfi)
			{
				for( int nIndex = 0; nIndex < mfi->NumFiles(); nIndex++ )
				{
					UStr path;
					UStr fileName;
					UStr currRev;

					mfi->get(nIndex, path, fileName, currRev);
					if( !path.empty() && !path.endsWith(kPathDelimiter) )
						path << kPathDelimiter;

					path << fileName;

					UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kComboDiffSelection,
						  (void *)(const char*)path);
				}
			}
		}
		
	}
	else
	{
		UEventSendMessage(GetWidID(), EV_GETTEXT, kEditLog, &m_logmsg);
		if(!m_logmsg.empty())
			gCommitLogs.Insert(m_logmsg);
 	}
}

int UCvsCommit_MAIN::OnExecDiff(void)
{
	if( m_mf && m_mf->TotalNumFiles() )
	{
		MultiFiles::const_iterator mfi;
		int diff_idx;
		for( diff_idx = 0, mfi = m_mf->begin(); mfi != m_mf->end(); ++mfi)
		{
			for( int nIndex = 0; nIndex < mfi->NumFiles(); nIndex++, diff_idx++ )
			{
				if( diff_idx == m_selected_diff_file) 
				{
					UStr path;
					UStr fileName;
					UStr currRev;

					mfi->get(nIndex, path, fileName, currRev);

					MultiFiles mf;
					mf.newdir(path);
					mf.newfile(fileName, 0, currRev);
						
					KoDiffHandler handler;
					handler.OnFiles(&mf);
					return 0;
				}
			}
		}
		// oops! file not found!
	}
	return 0;
}

// - - - - - - - - - - - - - - - - - - - - -

class UCvsCommit_OPTIONS : public UWidget
{
	UDECLARE_DYNAMIC(UCvsCommit_OPTIONS)
public:
	UCvsCommit_OPTIONS( const MultiFiles *mf);
	virtual ~UCvsCommit_OPTIONS() {}

	enum
	{
		kNoRecurse = EV_COMMAND_START,	// 0
		kForceCommit,				// 1
		kForceRevision,     // 2
		kRevList,                // 3
		kBrowseRev, // 4
		kForceRecurse, // 5
		kNoRunModuleProgram, // 6
		kCheckValidEdits, // 7
	};

	virtual void DoDataExchange(bool fill);

	bool	m_forceCommit;
	bool	m_forceRecurse;
	bool	m_forceRevision;
	bool	m_noModuleProgram;
	bool	m_noRecurse;
	bool	m_checkValidEdits;
	UStr	m_rev;

protected:

	ev_msg int OnBrowseRevision(void);
	ev_msg int OnForceHit(void);
	ev_msg void OnRevisionSel(int pos, const char *txt);

	UDECLARE_MESSAGE_MAP()
private:
	const MultiFiles* m_mf;
	bool m_localforcerev;
};

UIMPLEMENT_DYNAMIC(UCvsCommit_OPTIONS, UWidget)

UBEGIN_MESSAGE_MAP(UCvsCommit_OPTIONS, UWidget)
	ON_UCOMMAND(UCvsCommit_OPTIONS::kBrowseRev, UCvsCommit_OPTIONS::OnBrowseRevision)
	ON_UCOMMAND(UCvsCommit_OPTIONS::kForceRevision, UCvsCommit_OPTIONS::OnForceHit)
	ON_COMBO_SEL(UCvsCommit_OPTIONS::kRevList, UCvsCommit_OPTIONS::OnRevisionSel)
UEND_MESSAGE_MAP()

UCvsCommit_OPTIONS::UCvsCommit_OPTIONS( const MultiFiles *mf)
	: m_mf(mf), m_localforcerev(false), UWidget(::UEventGetWidID())
{
	m_forceCommit = false;
	m_forceRecurse = false;
	m_forceRevision = false;
	m_noModuleProgram = false;
	m_noRecurse = false;
	m_checkValidEdits = (bool)gCheckValidEdits;
}

void UCvsCommit_OPTIONS::DoDataExchange(bool fill)
{
	if(fill)
	{
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kForceCommit, m_forceCommit), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kForceRecurse, m_forceRecurse), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kForceRevision, m_forceRevision), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kNoRunModuleProgram, m_noModuleProgram), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kNoRecurse, m_noRecurse), 0L);
		UEventSendMessage(GetWidID(), EV_SETSTATE, UMAKEINT(kCheckValidEdits, m_checkValidEdits), 0L);

		UEventSendMessage(GetWidID(), EV_COMBO_RESETALL, kRevList, 0L);
		const std::vector<UStr> & list = gRevNames.GetList();
		std::vector<UStr>::const_iterator i;
		for(i = list.begin(); i != list.end(); ++i)
		{
			UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kRevList, (void *)(const char *)(*i));
		}
		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kRevList, m_forceRevision), 0L);
		UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kBrowseRev, m_forceRevision), 0L);
	}
	else
	{
		m_forceCommit = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kForceCommit, 0L);
		m_forceRecurse = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kForceRecurse, 0L);
		m_forceRevision = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kForceRevision, 0L);
		m_noModuleProgram = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kNoRunModuleProgram, 0L);
		m_noRecurse = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kNoRecurse, 0L);
		m_checkValidEdits = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kCheckValidEdits, 0L);
		UEventSendMessage(GetWidID(), EV_GETTEXT, kRevList, &m_rev);
	}
}

ev_msg int UCvsCommit_OPTIONS::OnBrowseRevision(void)
{
	CStr tagName;
	if( CompatGetTagListItem(m_mf, tagName) )
	{
		m_rev = (const char*)tagName;
		if( !m_rev.empty())
		{
			UEventSendMessage(GetWidID(), EV_COMBO_APPEND, kRevList, (void *)(const char *)tagName);
			int sel = UEventSendMessage(GetWidID(), EV_COMBO_GETCOUNT, kRevList, 0L);
			UEventSendMessage(GetWidID(), EV_COMBO_SETSEL, UMAKEINT(kRevList, sel-1), 0L);
		}
	}
	return 0;
}

ev_msg int UCvsCommit_OPTIONS::OnForceHit(void)
{
	int state = UEventSendMessage(GetWidID(), EV_QUERYSTATE, kForceRevision, 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kRevList, state), 0L);
	UEventSendMessage(GetWidID(), EV_ENABLECMD, UMAKEINT(kBrowseRev, state), 0L);
	return 0;
}

void UCvsCommit_OPTIONS::OnRevisionSel(int pos, const char *txt)
{
	m_rev = txt;
}


#endif // qUnix

/*!
	Get the commit options
	\note The path is used to read the /path/CVS/Template optionally
*/
bool CompatGetCommit(const MultiFiles* mf,
					 CStr& commit, bool& norecurs, 
					 bool& forceCommit, bool& forceRecurse, CPStr& rev, bool& noModuleProgram,
					 bool& checkValidEdits,
					 const char* pathTmpl /*= 0L*/)
{
	bool userHitOK = false;
	
	static CStr sLastMsg;
	static CPStr sLastRev;
	
	bool hasRev = false;
	
	norecurs = false;
	commit = "";
	forceCommit = false;
	forceRecurse = false;
	rev = "";
	noModuleProgram = false;
	checkValidEdits = (bool)gCheckValidEdits;

#ifdef WIN32
	CPropertySheet pages("Commit settings");
	pages.m_psh.dwFlags |= PSH_NOAPPLYNOW;
	CCommit_MAIN page1(mf, sLastMsg, pathTmpl);
	CCommit_OPTIONS page2(mf);
	CGetPrefs_GLOBALS page3;
	pages.AddPage(&page1);
	pages.AddPage(&page2);
	pages.AddPage(&page3);
	if(pages.DoModal() == IDOK)
	{
		commit = (const char *)page1.m_logMsg;
		sLastMsg = (const char *)page1.m_logMsg;
		norecurs = page2.m_noRecurse ? true : false;
		forceCommit = page2.m_forceCommit ? true : false;
		forceRecurse = page2.m_forceRecurse ? true : false;
		hasRev = page2.m_forceRevision ? true : false;
		sLastRev = page2.m_rev;
		noModuleProgram = page2.m_noModuleProgram ? true : false;
		gCheckValidEdits = page2.m_checkValidEdits ? true : false;
		page3.StoreValues();

		userHitOK = true;
	}
#endif /* WIN32 */
#ifdef qMacCvsPP
	StDialogHandler	theHandler(dlg_Commit, LCommander::GetTopCommander());
	LWindow *theDialog = theHandler.GetDialog();
	ThrowIfNil_(theDialog);
	static UInt16 sRuntimePanel = 1;
	
	LMultiPanelView *multiView = dynamic_cast<LMultiPanelView*>
		(theDialog->FindPaneByID(item_MultiViewPrefs));
	LPane *groupView = theDialog->FindPaneByID(item_GroupViewPrefs);
	
	multiView->SwitchToPanel(3);
	DoDataExchange_Globals(theDialog, true);
	multiView->SwitchToPanel(2);
	DoDataExchange_CommitOptions(theDialog, mf, norecurs, forceCommit, forceRecurse, hasRev, sLastRev, noModuleProgram, true);
	multiView->SwitchToPanel(1);
	DoDataExchange_Commit(theDialog, sLastMsg, pathTmpl, true);

	LPopupButton *previewDiff = dynamic_cast<LPopupButton*>
								(theDialog->FindPaneByID(item_PreviewDiffPopup));
	LPushButton *previewBtn = dynamic_cast<LPushButton*>
								(theDialog->FindPaneByID(item_PreviewDiffBtn));
	previewBtn->AddListener(&theHandler);
	previewDiff->DeleteAllMenuItems();
	if( mf && mf->TotalNumFiles() )
	{
		MultiFiles::const_iterator mfi;
		for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
		{
			for( int nIndex = 0; nIndex < mfi->NumFiles(); nIndex++ )
			{
				UStr path;
				UStr fileName, currRev;

				mfi->get(nIndex, path, fileName, currRev);
				if(!path.empty() && !path.endsWith(kPathDelimiter))
					path << kPathDelimiter;
				path << fileName;
				previewDiff->InsertMenuItem(LStr255(path), ::CountMenuItems(previewDiff->GetMacMenuH()));
			}
		}
	}
	else
	{
		previewDiff->Disable();
		previewBtn->Disable();
	}

	groupView->SetValue(sRuntimePanel);
	theDialog->Show();
	MessageT hitMessage;
	while (true)
	{		// Let DialogHandler process events
		hitMessage = theHandler.DoDialog();
		
		if (hitMessage == msg_OK || hitMessage == msg_Cancel)
			break;
		
		if(hitMessage == item_PreviewDiffBtn)
		{
			int file = previewDiff->GetCurrentMenuItem(), count = 1;
			MultiFiles::const_iterator mfi;
			for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
			{
				for( int nIndex = 0; nIndex < mfi->NumFiles(); nIndex++, count++)
				{
					if(count == file)
					{
						UStr path;
						UStr fileName, currRev;
						mfi->get(nIndex, path, fileName, currRev);
						
						MultiFiles mf;
						mf.newdir(path);
						mf.newfile(fileName, 0, currRev);
						
						KoDiffHandler handler;
						handler.OnFiles(&mf);
						break;
					}
				}
			}
		}
	}
	theDialog->Hide();
	sRuntimePanel = groupView->GetValue();
	
	if(hitMessage == msg_OK)
	{
		multiView->SwitchToPanel(1);
		DoDataExchange_Commit(theDialog, sLastMsg, pathTmpl, false);
		multiView->SwitchToPanel(2);
		DoDataExchange_CommitOptions(theDialog, mf, norecurs, forceCommit, forceRecurse, hasRev, sLastRev, noModuleProgram, false);
		multiView->SwitchToPanel(3);
		DoDataExchange_Globals(theDialog, false);
		commit = sLastMsg;
		commit.replace('\015', '\012');
		userHitOK = true;
	}
#endif /* qMacCvsPP */
#if qUnix
	void *wid = UCreate_CommitDlg();

	UCvsCommit *dlg = new UCvsCommit();
	UCvsCommit_MAIN *tab1 = new UCvsCommit_MAIN( mf, sLastMsg, pathTmpl);
	UCvsCommit_OPTIONS *tab2 = new UCvsCommit_OPTIONS( mf);

	UEventSendMessage(dlg->GetWidID(), EV_INIT_WIDGET, kUMainWidget, wid);	
	dlg->AddPage(tab1, UCvsCommit::kTabGeneral, 0);
	dlg->AddPage(tab2, UCvsCommit::kTabGeneral, 1);

	if(dlg->DoModal())
	{
		commit = tab1->m_logmsg;
		sLastMsg = tab1->m_logmsg;
		norecurs = tab2->m_noRecurse;
		forceCommit = tab2->m_forceCommit;
		forceRecurse = tab2->m_forceRecurse;
		hasRev = tab2->m_forceRevision;
		sLastRev = tab2->m_rev;
		noModuleProgram = tab2->m_noModuleProgram;
		gCheckValidEdits = tab2->m_checkValidEdits;

		userHitOK = true;
	}

	delete dlg;
#endif // qUnix

	if(userHitOK)
	{
		if(commit.length() == 0)
			commit = "no message";
		
		if(hasRev && !sLastRev.empty())
			rev = sLastRev;

		checkValidEdits = (bool)gCheckValidEdits;

		gCvsPrefs.save();
	}

	return userHitOK;
}
