/*
 * Copyright 1997, 1999 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

#include "stdafx.h"
#include "p4win.h"
#include "P4SpecDlg.h"
#include "cmd_editspec.h"
#include "TokenString.h"
#include "SpecDescDlg.h"
#include "Cmd_SendSpec.h"
#include "DeltaView.h"
#include "LabelView.h"
#include "BranchView.h"
#include "ClientView.h"
#include "JobView.h"
#include "UserView.h"
#include "MainFrm.h"
#include "MsgBox.h"
#include "newclientdlg.h"

#include "p4client.h"
#include "p4job.h"
#include "p4branch.h"
#include "p4user.h"
#include "p4label.h"

#include "cmd_diff.h"

#include <afxdisp.h>
#include "hlp\p4win.hh"


#define P4BRANCH_SPEC	2
#define P4CHANGE_SPEC	3
#define P4CLIENT_SPEC	4
#define P4DEPOT_SPEC	5
#define P4JOB_SPEC		6
#define P4LABEL_SPEC	7
#define P4PROTECT_SPEC	8
#define P4USER_SPEC		9

#define	MINHEIGHT	  240
#define	EXTRAHEIGHT		2

const CString g_tagView = _T("View");
const CString g_tagFile = _T("Files");
const CString g_tagJob  = _T("Job");
const CString g_tagJobStatus  = _T("JobStatus");
const CString g_tagRoot = _T("Root");
const CString g_tagAltRoots = _T("AltRoots");
const CString g_tagReviews = _T("Reviews");
const CString g_tagStatus = _T("Status");
const CString g_tagUser = _T("User");
const CString g_tagDescription = _T("Description") ;
const CString g_tagFullName =  _T("FullName");
const CString g_tagPassword = _T("Password");


const int JOB_CODE_NAME = 101;
const int JOB_CODE_STATUS = 102;
const int JOB_CODE_USER = 103;
const int JOB_CODE_DATE = 104;
const int JOB_CODE_DESC = 105;


//		combo box stuff. i'm ashamed of this code, so don't look at it.
//
const CString g_SelectionSeparator = _T(":");
const CString g_PresetSeparator = _T("/");

const int g_DropListSize= 10;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CP4SpecDlg dialog


IMPLEMENT_DYNCREATE(CP4SpecDlg, CPropertyPage)

CP4SpecDlg::CP4SpecDlg() : CPropertyPage(CP4SpecDlg::IDD)
{
	//{{AFX_DATA_INIT(CP4SpecDlg)
	//}}AFX_DATA_INIT
	m_bIsModal = FALSE;
	m_NumMultiLineChildWnds = 0;
	m_jobList = m_fileList = 0;
	m_SpecType=0;
	m_pFirstControl=NULL;
	m_pFocusControl=NULL;
	m_AllowSubmit=FALSE;
	m_SendingSpec=FALSE;
	m_AddFilesControl=TRUE;
	m_WindowShown =FALSE;
	m_EditorBtnDisabled = m_ChangesHaveBeenMade = FALSE;
	m_SetFocusHere = FALSE;
	m_AutomaticallyUpdate = FALSE;
	m_HasRequired = FALSE;
	m_BrowseShown = FALSE;
	m_BrowseBtnCtrlID = -1;
	m_PrevCBPmt = "";
	m_Root2Use = _T("");
	m_MinSize= CSize(0,MINHEIGHT);
	m_VscrollMax = 1000;
	m_VscrollPos = 0;
	m_pDeltaView = MainFrame()->GetDeltaView();
	m_pLastFilesList = 0;
	m_MinLi = _tstoi(GET_P4REGPTR()->GetMinMultiLineSize());
	if (m_MinLi < 2)
		m_MinLi = 3;	// set back to default

	RECT rect;
	SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
	m_ScreenHeight = rect.bottom - rect.top;

	HDC hDC = ::GetDC(NULL);
	m_LogPixelsX = GetDeviceCaps(hDC, LOGPIXELSX);
	::ReleaseDC(NULL, hDC);
}

CP4SpecDlg::~CP4SpecDlg()
{
	// can't use MainFrame()-> construct
	// because mainfram might have closed.
	CMainFrame * mainWnd = MainFrame();
	if (mainWnd)
		mainWnd->SetGotUserInput( );
}

void CP4SpecDlg::SetCallingCommand( CCmd_EditSpec *pCmd )
{
	ASSERT_KINDOF( CCmd_EditSpec, pCmd );
	m_pCallingCommand= pCmd;
}

BOOL CP4SpecDlg::SetSpec(LPCTSTR spec, LPCTSTR specDefStr
						 , int specType, BOOL allowSubmit)
{
	// Initialize CWinPos registry class
	switch(specType)
	{
        case P4BRANCH_SPEC:
			m_WinPos.SetWindow( this, _T("BranchSpecDlg") );
            break;
		case P4CHANGE_SPEC:
			m_WinPos.SetWindow( this, _T("ChangeSpecDlg") );
            break;
        case P4CLIENT_SPEC:
            m_WinPos.SetWindow( this, _T("ClientSpecDlg") );
            break;
		case P4JOB_SPEC:
            m_WinPos.SetWindow( this, _T("JobSpecDlg") );
            break;
        case P4LABEL_SPEC:
            m_WinPos.SetWindow( this, _T("LabelSpecDlg") );
            break;
        case P4USER_SPEC:
            m_WinPos.SetWindow( this, _T("UserSpecDlg") );
            break;
		default:
			ASSERT(0);
    }

	m_OldForm = spec;
    m_SpecDefStr= specDefStr;
	m_OrigPassword.Empty();
	m_OrigRoot.Empty();

    // At least some 97.3 servers seem to omit the Labelspec 'Options' tag
    // and the Userspec 'Jobview' tag, yet send specs that include these 
    // values.  Since the servers are already in circulation, fix this at
    // the client side
    if( GET_SERVERLEVEL() < 4 )
    {
        switch(specType)
        {
        case P4BRANCH_SPEC:
            m_SpecDefStr= BRANCH_SPEC;
            break;
        case P4CLIENT_SPEC:
            m_SpecDefStr= CLIENT_SPEC;
            break;
        case P4LABEL_SPEC:
            m_SpecDefStr= LABEL_SPEC;
            break;
        case P4USER_SPEC:
            m_SpecDefStr= USER_SPEC;
            break;
        }
    }
	
	m_SpecType = specType;
	m_AllowSubmit = allowSubmit;

	//		the big kahuna: get the existing spec info.
	//		this code used to be in initdialog but
	//		if there was an error in the spec, it would
	//		put up an empty dialog. very ugly. this is
	//		called before DoModal, so things look better.
	//
	return ParseSpecIntoForm( );
}


/*
	_________________________________________________________________

	Parse( ) calls pMyspecData's Set( ) function
	for as many times as there are elements in the spec
	_________________________________________________________________
*/

BOOL CP4SpecDlg::ParseSpecIntoForm( )
{
	Error e;
	StrRef sSpecDefStr;

	try
	{
		if( GET_SERVERLEVEL() >= 16)
			ReorderSpecDefString( m_OldForm, m_SpecDefStr );

		if ((m_FoundLineElemWithValues = FindLineElemWithValues( m_SpecDefStr )) != -1)
			HandleLineElemWithValues( m_OldForm, m_SpecDefStr );

		m_SpecData.SetSpecElems( m_SpecDefStr );
        m_SpecDefStrA = CharFromCString(m_SpecDefStr);
        sSpecDefStr.Set( const_cast<char*>((const char*)m_SpecDefStrA) );
		m_Spec.Decode( &sSpecDefStr, &e );
		ASSERT( ! e.Test ( ) );

		e.Clear( );
        m_OldFormA = CharFromCString(m_OldForm);
        m_Spec.ParseNoValid( const_cast<char*>((const char*)m_OldFormA), &m_SpecData, &e );

		if( e.Test( ) )
		{
			CString msg= FormatError(&e);

			// A missing field is not a fatal error!
			if( msg.Find(_T("Missing required field")) == -1 )
			{
				CString txt;
				txt.FormatMessage(IDS_ERROR_IN_SPECIFICATION_s, msg);
				AddToStatus( txt, SV_WARNING, true );
				return FALSE;
			}
		}

		//		all fields have to be in the dialogue, even those
		//		that are empty. if there's an empty one at the
		//		end, the ParseNoValid didn't call specdata's Get
		//		enough number of times. so make sure all fields are there
		//		except, of course, Jobs and Files from new changelists! damn.
		//
		m_SpecData.CheckForStragglers( );
	}

	catch( ... )
	{
		CString msg;
		msg.LoadString(IDS_BAD_SPEC);
		AddToStatus(msg, SV_WARNING, true );
		return FALSE;
	}

    // Store any available instruction text for tooltip support.  This text
    // will be rummaged during OnNotifyToolTip() to see if we have a helpful
    // hint to offer the user.  To get the comments, find the first blank line
    // and then grab all text before that line.
    
    int i, j = 0;
    m_InstructionText.Empty();
        
    for( i=0; i < m_OldForm.GetLength()-2; i++)
    {
        if( m_OldForm[i] == _T('\n') )
        {
            m_InstructionText+= g_CRLF;
            BOOL found = FALSE;

            // Look for another '\n' before non-white-space
            for( j=i+1; !found && j<m_OldForm.GetLength()-1; j++ )
            {
                switch( m_OldForm[j])
                {
                case _T('\t'):
                case _T(' '):
                case _T('\r'):
                    break;

                case _T('\n'):
                default:
                    found=TRUE;
                    break;
                }
            }
             
            // If the first non-white-space we found was another \n,
            // we are at the end of comments (it's j-1, because the for
            // loop increments j before the loop test)
            if( m_OldForm[j-1] == _T('\n') )
            {
                break;  // out of for
            }
        }
        else
            m_InstructionText+= m_OldForm[i];
    }
    
    // If we didnt find a blank line, we dont know where the comments end.  
    // In this case, clear the instruction text rather than allow a botched display.
    if( i > j )
        m_InstructionText.Empty();

	return TRUE;
}

void CP4SpecDlg::ReorderSpecDefString( CString &form, CString &specDefStr )
{
	if ((specDefStr.Find(_T(";seq:")) == -1) 
	 && (specDefStr.Find(_T(";type:wlist")) == -1) 
	 && (specDefStr.Find(_T(";type:llist")) == -1))
		return;

	CString oldspec = specDefStr;
	CString oldform = form;
	CString elm;
	CString fld;
	CStringList nonseqelms;
	CStringList nonseqlists;
	CStringArray seqelms;
	CStringArray fields;

	seqelms.SetSize(20, 10);

	// split the data in 2 parts:
	//	1)	'form' contains the header
	//	2)	'oldform' contains the data
	int f = oldform.Find(_T("\n\n"));
	if (f == -1)
		return;
	form = form.Left(f);
	oldform = oldform.Mid(f);

	// loop thru the spec, getting each element
	// save elements with "seq:" in their corresponding slot in the seqelms array
	// save elements without "seq:" in the nonseqelms list
	int n;
	int nbrflds=0;
	int nbrelms=0;
	int m=0;
	int i=0;
	while ((i = oldspec.Find(_T(";;"))) != -1)
	{
		// isolate the next spec element
		i += 2;
		elm = oldspec.Left(i);
		oldspec = oldspec.Mid(i);

		// if this element has "seq:", put it in the 'seqelms' array
		// otherwise put it at the end of the 'nonseqelms' list if not a list
		// or at the end of the 'nonseqlists' list if is a 'wlist' or 'llist'
		int j;
		if ((j = elm.Find(_T(";seq:"))) != -1)
		{
			CString seq = elm.Mid(j+sizeof(_T(";seq"))/sizeof(TCHAR));
			n = _tstoi(seq);
			if (n)
			{
				seqelms.SetAtGrow(n, elm);
				m = max(n, m);
			}
			else	// special case - seq# of 0 means no seq number provided
				nonseqelms.AddTail(elm);
		}
		else if ((elm.Find(_T(";type:wlist")) != -1)
			  || (elm.Find(_T(";type:llist")) != -1))
		{
			nonseqlists.AddTail(elm);
		}
		else
		{
			nonseqelms.AddTail(elm);
		}
		nbrelms++;

		// we can do the fields in the same loop 
		// since there are always at least as many
		// spec elms as fields (maybe more)
		f = oldform.Find(_T("\n\n"), 2);
		if (f == -1)
			f = oldform.GetLength();
		fld = oldform.Left(f);
		oldform = oldform.Mid(f);
		if (!fld.IsEmpty())
		{
			fields.Add(fld);
			nbrflds++;
		}
	}

	// if the greatest seq# is larger than the count of elements
	// we are in trouble - bail and use the order of the spec
	if (m > nbrelms)
	{
		// we have to restore the form data before bailing
		for (int j = -1; ++j < nbrflds; )
		{
			fld = fields.GetAt(j);
				form += fld;
		}
		CString txt;
		txt.FormatMessage(IDS_SEQGTRNBRELMS_d_d, m, nbrelms);
		AfxMessageBox(txt, MB_ICONEXCLAMATION);
		return;
	}

	// if we found any elements without "seq:",
	// put them in the gaps in the seqelms array
	if (!nonseqelms.IsEmpty())
	{
		for (n=0; ++n <= nbrelms; )
		{
			if (n >= seqelms.GetSize() || seqelms[n].IsEmpty())
			{
				seqelms.SetAtGrow(n, nonseqelms.GetHead());
				nonseqelms.RemoveHead();
				if (nonseqelms.IsEmpty())
					break;
			}
		}
	}
	// do the same for any lists
	if (!nonseqlists.IsEmpty())
	{
		for (n=0; ++n <= nbrelms; )
		{
			if (n >= seqelms.GetSize() || seqelms[n].IsEmpty())
			{
				seqelms.SetAtGrow(n, nonseqlists.GetHead());
				nonseqlists.RemoveHead();
				if (nonseqlists.IsEmpty())
					break;
			}
		}
	}

	// Rebuild the spec and the data form
	specDefStr.Empty();
	for (n=0; ++n <= nbrelms; )
	{
		// the spec elements are in order in the seqelms array
		// so just concatenate them together
		elm = seqelms.GetAt(n);
		specDefStr += elm;
		// for the data form, we need to find the data
		// that corresponds to the current spec element,
		// so get the name of the current spec element
		i = elm.Find(_T(';'));
		elm = _T('\n') + elm.Left(i) + _T(':');
		// search thru the data fields for that name
		for (int j = -1; ++j < nbrflds; )
		{
			fld = fields.GetAt(j);
			if (fld.Find(elm) != -1)
			{
				// if (there might not be any data for a field) we find it
				// concatenate that field to the form
				form += fld;
				break;
			}
		}
	}
}

int CP4SpecDlg::FindLineElemWithValues( CString specDefStr, int offset /*= 0*/ )
{
	int typeline, values, dblsemicolon;
	while ((typeline = specDefStr.Find(_T(";type:line;"), offset)) != -1)
	{
		if ((dblsemicolon = specDefStr.Find(_T(";;"), typeline)) == -1)
			return -1;
		if (((values = specDefStr.Find(_T(";val:"), typeline)) < dblsemicolon)
		  && (values > typeline))
			return typeline;
		offset = dblsemicolon;
	}
	return -1;
}

void CP4SpecDlg::HandleLineElemWithValues( CString form, CString specDefStr )
{
	int i, j;
	int offset;
	int values;
	int counter;
	TCHAR next;
	CString newform;
	CString newspec;
	CString newelem;
	CString fldname;
	CString dspname;
	CString txt;

	m_OrigSpecDefStr = specDefStr;
	for (offset = 0; (offset = FindLineElemWithValues(specDefStr, offset)) != -1; 
								offset += sizeof(_T(";type:line;"))/sizeof(TCHAR))
	{
		newspec = specDefStr.Left(offset);
		while ((i = newspec.ReverseFind(_T(';'))) != -1)
		{
			if (newspec.GetAt(i-1) == _T(';'))
				break;
			newspec = newspec.Left(i);
		}
		if (i == -1)	// spec doesn't match what we expect!
			return;
		dspname = fldname = newspec.Right(newspec.GetLength() - i - 1);
		dspname += CString((TCHAR)0x10);
		newspec = newspec.Left(i+1);
		values = specDefStr.Find(_T(";val:"), offset) + sizeof(_T(";val:"))/sizeof(TCHAR)-1;
		counter = 1;
   		do
   		{
			// Set the format for the 1st of the group to "L";
			// set the format for the rest of the group to "R".
			// This will be fixed up by CSpecData::AddElem()
			// which converts from the new L, R, I codes back
			// to the internal BH, MH, IH and RH codes
			CString fmt = (counter == 1) ? _T("L") : _T("R");

 			i = specDefStr.Find(_T('/'), values);
 			if (i == -1)
 			{
				newelem.Format(_T("%s%d;type:select;len:20;fmt:%s;pre:"), 
					dspname, counter, fmt);
 				newspec += newelem + _T(";val:") + _T(" /");
 			}
 			else
 			{
	   			newelem.Format(_T("%s%d;type:select;rq;len:20;fmt:%s;pre:"), 
					dspname, counter, fmt);
   				newspec += newelem + specDefStr.Mid(values, i-values) + _T(";val:");
 			}
			counter++;
			i = specDefStr.Find(_T(','), values);
			j = specDefStr.Find(_T(';'), values);
			if ((i == -1) || (j < i))
				 i = j;
			newspec += specDefStr.Mid(values, i-values) + _T(";;");
			next = specDefStr.GetAt(i);
			specDefStr = specDefStr.Mid(i+1);
			values = 0;
		} while (next == _T(','));
		newspec += specDefStr.Mid(1);

		txt.Format(_T("\n\n%s:\t"), fldname);
		i = form.Find(txt);
		if (i == -1)	// data doesn't match spec!
		{
			m_FoundLineElemWithValues = -1;
			return;
		}
		newform = form.Left(i);
		values  = i + txt.GetLength();
		counter = 1;
		do
		{
			newelem.Format(_T("\n\n%s%d:\t"), dspname, counter++);
			i = form.Find(_T(' '), values);
			j = form.Find(_T('\n'), values);
			if ((i == -1) || (j < i))
				 i = j;
			newform += newelem + form.Mid(values, i-values);
			next = form.GetAt(i);
			form = form.Mid(i+1);
			values = 0;
		} while (next == _T(' '));
		newform += _T("\n\n") + form.Mid(1);
		specDefStr = newspec;
		form = newform;
	}
	m_SpecDefStr = newspec;
	m_OldForm = newform;
}

BOOL CP4SpecDlg::RestoreLineElemWithValues( CString form, CString specDefStr )
{
	int i;
	int offset;
	int values;
	int counter;
	CString newform;
	CString fldname;
	CString dspname;
	CString temp;
	CString txt;

	for (offset = 0; (offset = FindLineElemWithValues(specDefStr, offset)) != -1; 
								offset += sizeof(_T(";type:line;"))/sizeof(TCHAR))
	{
		counter = 0;
		temp = specDefStr.Left(offset);
		i = temp.ReverseFind(_T(';'));
		while ((i = temp.ReverseFind(_T(';'))) != -1)
		{
			if (temp.GetAt(i-1) == _T(';'))
				break;
			temp = temp.Left(i);
		}
		if (i == -1)	// data doesn't match spec!
			return FALSE;
		dspname = fldname = temp.Right(temp.GetLength() - i - 1);
		dspname += CString((TCHAR)0x10);
		txt.Format(_T("\n\n%s%d:\t"), dspname, ++counter);
		i = form.Find(txt);
		if (i == -1)	// data doesn't match spec!
			return FALSE;
		newform = form.Left(i);
		values  = i + txt.GetLength();
		txt.Format(_T("\n\n%s:\t"), fldname, counter);
		newform += txt;
		do
		{
			if (counter > 1)
				newform += _T(' ');
			i = form.Find(_T('\n'), values);
			if (i == -1)	// data doesn't match spec!
				return FALSE;
			newform += form.Mid(values, i-values);
			form = form.Mid(i);
			txt.Format(_T("\n\n%s%d:\t"), dspname, ++counter);
			i = form.Find(txt);
			values = i + txt.GetLength();
		} while (i != -1);
		newform += form;
		form = newform;
	}
	m_NewForm = newform;
	m_SpecDefStr = specDefStr;
	return TRUE;
}


/*
	_________________________________________________________________
*/

void CP4SpecDlg::SetChangeParms(BOOL checkAllJobs, 
								BOOL allowSubmit, 
								BOOL checkOnlyChgedFiles/*=FALSE*/,
								BOOL addFilesControl/*=TRUE*/,
								BOOL submitOnlySelected/*=FALSE*/,
								BOOL automaticallyUpdate/*=FALSE*/)
{
	m_CheckAllJobs= checkAllJobs;
	m_AllowSubmit=allowSubmit;
	m_CheckOnlyChgedFiles=checkOnlyChgedFiles;
	m_AddFilesControl=addFilesControl;
	m_SubmitOnlySelected=submitOnlySelected;
	m_AutomaticallyUpdate=automaticallyUpdate;
}


void CP4SpecDlg::SetClientParms(LPCTSTR root, BOOL automaticallyUpdate/*=FALSE*/)
{
	m_Root2Use= root;
	m_AutomaticallyUpdate=automaticallyUpdate;
}


LPCTSTR CP4SpecDlg::GetSpec()
{
	return m_NewForm;
}


void CP4SpecDlg::DoDataExchange(CDataExchange* pDX)
{
	CPropertyPage::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CP4SpecDlg)
	DDX_Control(pDX, IDC_BUSYMESSAGE, m_BusyMessage);
	DDX_Control(pDX, IDC_REQSTATIC, m_ReqStatic);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CP4SpecDlg, CPropertyPage)
	//{{AFX_MSG_MAP(CP4SpecDlg)
	ON_WM_SETCURSOR()
	ON_WM_SIZE()
	ON_WM_SIZING()
	ON_WM_SHOWWINDOW()
	ON_WM_VSCROLL()
	ON_WM_GETMINMAXINFO()
	ON_WM_HELPINFO()
	ON_COMMAND(ID_FILE_CANCEL, OnCancelButton)
	ON_COMMAND(IDC_BROWSE, OnBrowse)
	//}}AFX_MSG_MAP
    ON_NOTIFY_EX(TTN_NEEDTEXT,0,OnToolTipNotify)
	ON_MESSAGE(WM_P4SENDSPEC, OnP4SendSpec)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CP4SpecDlg message handlers

BOOL CP4SpecDlg::OnInitDialog() 
{
	ASSERT( m_specControls.GetSize() == 0 );
	
	//		create the m_hWnd.
	//
	CPropertyPage::OnInitDialog();

    EnableToolTips(TRUE);
	CreateUserFonts();

    SetScrollRange(SB_VERT, 0, m_VscrollMax, FALSE);
    SetScrollPos(SB_VERT, m_VscrollPos = 0, TRUE);
	
	//		Calculate some dimensions for control placement
	//
	RECT rectdlg, rect1, rect2, rect3, rectlist;
	CWnd *static1 = GetDlgItem( IDC_STATIC1 );  // invisible static in the dlg resource
	CWnd *static2 = GetDlgItem( IDC_STATIC2 );	// invisible static in the dlg resource
	CWnd *static3 = GetDlgItem( IDC_STATIC3 );	// invisible static in the dlg resource
	CWnd *droplist= GetDlgItem( IDC_COMBO1 );   // invisible combo in the dlg resource

	//		Store the dialog width
	//
	GetClientRect( &rectdlg );
	m_Width = rectdlg.right;       
	
	//		store the standard and maximum dimensions of the controls
	//
	static1->GetWindowRect( &rect1 );
	static2->GetWindowRect( &rect2 );
	static3->GetWindowRect( &rect3 );
	droplist->GetWindowRect( &rectlist );
	ScreenToClient( &rect1 );
	ScreenToClient( &rect2 );
	ScreenToClient( &rect3 );

	int lenlongestprompt = GetLengthLongestPrompt();
	m_StdHeight = rect1.bottom - rect1.top;		// standard static, edit dimension
	m_StdWidth  = min(rect1.right - rect1.left, lenlongestprompt); // prompt width
	// Adjust for scaling that will be done in SetUserFont()
	if (m_OldAveCharWidth != m_NewAveCharWidth)
		m_StdWidth = (m_StdWidth*m_OldAveCharWidth + m_NewAveCharWidth*2) / m_NewAveCharWidth;
	m_MaxWidth  = rect3.right - rect3.left;		// max width that fits dlg
	m_StdSpaceV = rect2.top - rect1.bottom;		// vertical spacing
	m_StdSpaceH = 5;							// horizontal spacing

	int adjamt = GetSystemMetrics(SM_CXVSCROLL);
	if (m_LogPixelsX < 105)
		adjamt += adjamt/2;
	m_MaxWidth -= adjamt;

	m_X = m_Y = rect1.top;

	m_ComboWidth = m_MaxWidth - m_StdWidth;
	m_HlfWidth   = m_MaxWidth/2 - m_StdWidth - m_StdSpaceH*2;

	//		okay, add controls, buttons, caption, size the window to the
	//		controls and slap it onto the window!
	//
	if ( SetControls( ) )
	{
		CDialog *pParent = (CDialog *)GetParent();
		pParent->SetWindowText( GetDialogueCaption( ) );

		if ( m_pFocusControl != NULL )
		{
			GotoDlgCtrl(m_pFocusControl);
			if( m_pFocusControl->IsKindOf( RUNTIME_CLASS( CP4EditBox ) ) )
			{
				int i;
				CP4EditBox *pEdit = (CP4EditBox *)m_pFocusControl;
				CString txt;
				pEdit->GetWindowText(txt);
				if ((i = txt.Find(_T("\r\n\r\n"))) != -1)
				{
					pEdit->SetSel(i+2, txt.GetLength());
					pEdit->LineScroll(pEdit->LineFromChar(i));
				}
			}
		}
		else if ( m_pFirstControl != NULL )
			GotoDlgCtrl( m_pFirstControl );

		SetWindowPos( NULL, 0, 0, m_Width, m_Y , SWP_NOMOVE | SWP_NOZORDER );
		
		CenterWindow();
	}

	int thebottom = SetUserFont();
	if (m_MinSize.cy < thebottom)
		m_MinSize.cy = thebottom;

	if (m_AutomaticallyUpdate)
		On_OK();
	else
		m_pCallingCommand->ReleaseServerLock();

	return FALSE;  // return TRUE unless you set the focus to a control
				   //fanny: and i do, so i'm returning FALSE
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CP4SpecDlg::CreateUserFonts()
{
	// Create the user font 
	//
    LOGFONT logFont;
 
	GetFont()->GetLogFont(&logFont);
	logFont.lfHeight= -abs(GET_P4REGPTR()->GetFontSize());
    m_Font.CreateFontIndirect( &logFont );

	logFont.lfWeight += 200;
    m_FontBold.CreateFontIndirect( &logFont );

	lstrcpy(logFont.lfFaceName, GET_P4REGPTR()->GetFontFace());
	logFont.lfWeight -= 200;
	logFont.lfPitchAndFamily= FIXED_PITCH | FF_DONTCARE;
	logFont.lfCharSet = DEFAULT_CHARSET;
    m_FontFixed.CreateFontIndirect( &logFont );

	// Get text metrics for old font ( dialog template ) and new font
	//
	TEXTMETRIC tmOld, tmNew;
	CDC *pDC= GetDC();
	CFont *pOldFont= pDC->SelectObject( GetFont() );
	pDC->GetTextMetrics( &tmOld );
	pDC->SelectObject( &m_Font );
	pDC->GetTextMetrics( &tmNew );
	pDC->SelectObject( pOldFont );
	ReleaseDC( pDC );

	m_OldHeight= tmOld.tmHeight + tmOld.tmExternalLeading;
	m_NewHeight= tmNew.tmHeight + tmNew.tmExternalLeading;
	m_OldAveCharWidth = tmOld.tmAveCharWidth;
	m_NewAveCharWidth = tmNew.tmAveCharWidth;
}

int CP4SpecDlg::SetUserFont()
{
	// Resize the dialog, holding the topleft corner position
	//
	CRect clientRect, newClientRect, windowRect, newWindowRect;
	GetWindowRect( windowRect );
	GetClientRect( clientRect );

	int xDiff= windowRect.Width() - clientRect.Width();   // borders
	int yDiff= windowRect.Height() - clientRect.Height(); // borders

	newClientRect.left= newClientRect.top= 0;
	newClientRect.right= clientRect.right * m_NewAveCharWidth / m_OldAveCharWidth;
	newClientRect.bottom= clientRect.bottom * m_NewHeight / m_OldHeight;

	newWindowRect.left = windowRect.left;
	newWindowRect.top = windowRect.top;
	newWindowRect.right= windowRect.left + newClientRect.right + xDiff;
	newWindowRect.bottom= windowRect.top + newClientRect.bottom + yDiff;
	MoveWindow( newWindowRect );

	m_MinSize= CSize( newWindowRect.Width() - xDiff, max(MINHEIGHT,newWindowRect.Height() - yDiff));

	// And then set the new font
	//
 	SetFont(&m_Font);

	// Iterate thru all child windows, changing fonts and rescaling
	//
	int thebottom = m_Y - m_StdSpaceV;
	for( int i=0; i < m_childControls.GetSize(); i++ )
	{
		CWnd *pChildWnd= m_childControls[i].GetWindow( );

		pChildWnd->SetFont( (pChildWnd->IsKindOf( RUNTIME_CLASS(CP4EditBox) )
			&& m_childControls[i].GetType()==CHILD_MULTILINEEDIT) ? &m_FontFixed : &m_Font );
		pChildWnd->GetWindowRect( windowRect );
		ScreenToClient( windowRect );
		windowRect.left= windowRect.left * m_NewAveCharWidth / m_OldAveCharWidth;
		windowRect.right= windowRect.right * m_NewAveCharWidth / m_OldAveCharWidth;
		windowRect.top= windowRect.top * m_NewHeight / m_OldHeight;

		if( pChildWnd->IsKindOf( RUNTIME_CLASS( CComboBox ) ) )
		{
			windowRect.bottom= windowRect.top + ( m_StdHeight * g_DropListSize * m_NewHeight / m_OldHeight );
			thebottom = windowRect.top + m_StdHeight;
		}
		else
		{
			windowRect.bottom = thebottom = windowRect.bottom * m_NewHeight / m_OldHeight + 1;
		}
		
		pChildWnd->MoveWindow( windowRect );
		m_childControls[i].SetOrigRect( &windowRect );
	}
	thebottom += m_StdSpaceV*2;

	return thebottom;
}

BOOL CP4SpecDlg::OnToolTipNotify( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{   
    BOOL foundTip=FALSE;
    int i;

    TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;    
    UINT nID =pNMHDR->idFrom;

    if (pTTT->uFlags & TTF_IDISHWND)    
    {
        // idFrom is actually the HWND of the tool
        HWND hWnd= (HWND) nID;
        nID = ::GetDlgCtrlID((HWND)nID);        
        switch(nID)
        {
        case 0:
        case IDCANCEL:
        case IDOK:
        case IDALTERNATE:
            break;
        case ID_HELPNOTES:
			// can't just take a pointer to the CString returned by LoadStringResource
			// because it will be gone once we return, so make a copy of the first 80
			// bytes of it and hope the tooltip isn't any longer
			_tcsncpy(pTTT->szText, (LPCTSTR)LoadStringResource(IDS_SPEC_TOOLTIP), sizeof(pTTT->szText)/sizeof(TCHAR));
            pTTT->hinst = NULL;
            foundTip=TRUE;
            break;
        default:
			for( i=0; i<m_specControls.GetSize(); i++ )
            {
				SpecControl const & sc = m_specControls[i];
                CWnd *pWnd= sc.control;
				if(pWnd && hWnd == pWnd->m_hWnd && sc.tip.GetLength())
                {
                    pTTT->lpszText = (LPTSTR) LPCTSTR(sc.tip);
                    pTTT->hinst = NULL;
                    foundTip=TRUE;
                }
            }
            break;
        }    
    }
    return(foundTip);
}


/*
	_________________________________________________________________
*/

static DWORD SetBasicWinStyles( BOOL readOnly )
{
	DWORD style = WS_CHILD | WS_VISIBLE | ES_LEFT ;

	if( readOnly )
		style |= ES_READONLY; 
	else
		style |= WS_TABSTOP;

	return style;
}


/*
	_________________________________________________________________
*/

CStatic * SpecControl::CreateLabel ( CWnd *parent, LPCRECT rect, LPCTSTR prompt  )
{
	ASSERT(!label);
	label = new CStatic;
	ASSERT(label);
	label->Create( prompt, WS_CHILD | WS_VISIBLE, *rect, parent );
	return label;
}
CStatic * SpecControl::CreateLabel ( CWnd *parent )
{
	// create a hidden label

	ASSERT(!label);
	label = new CStatic;
	ASSERT(label);
	label->Create( _T("hidden"), WS_CHILD, CRect(0,0,1,1), parent );
	return label;
}

CComboBox * SpecControl::CreateCombo ( CWnd *parent, LPCRECT rect, DWORD style, HMENU menu )
{
	ASSERT(!control);
	CComboBox * pCombo = new CComboBox;
	ASSERT( pCombo );

	if ( !pCombo->CreateEx( WS_EX_CLIENTEDGE, _T("ComboBox")
		, _T(""), style, rect->left, rect->top
		, rect->right - rect->left + 1
		, ( rect->bottom - rect->top + 1 ) * g_DropListSize
		, parent->m_hWnd
		, menu ) )
	{
		delete pCombo;
		pCombo = 0;
	}
	control = pCombo;
	return pCombo;
}

CButton * SpecControl::CreateCheckBox(CWnd *parent, LPCRECT rect, 
									  DWORD style, int id, LPCTSTR prompt)
{
	ASSERT(!control);
	CButton * pButton = new CButton;
	ASSERT( pButton );

	CRect crect = rect;

	if ( !pButton->Create( prompt, style, crect, parent, id ) )
	{
		delete pButton;
		pButton = 0;
	}
	control = pButton;
	return pButton;
}

CReviewList * SpecControl::CreateList ( CWnd *parent, LPCRECT rect, DWORD style, HMENU menu, int code )
{
	ASSERT(!control);
	this->code = code;
	CReviewList * pList = new CReviewList;
	ASSERT( pList );

	if(!pList->CreateEx(WS_EX_CLIENTEDGE, MainFrame()->m_ReviewListClass
		, _T(""), style, rect->left, rect->top
		, rect->right - rect->left
		, rect->bottom - rect->top
		, parent->m_hWnd
		, menu))
	{
		delete pList;
		pList = 0;
	}
	control = pList;
	isChkList = TRUE;
	return pList;
}

CP4EditBox * SpecControl::CreateEdit(CWnd *parent,LPCRECT rect,DWORD style, HMENU menu,int code, 
									 BOOL allowDD/*=FALSE*/, int specType/*=0*/)
{
	ASSERT(!control);
	this->code = code;
	CP4EditBox * pEdit = new CP4EditBox(parent);
	ASSERT( pEdit );

	DWORD exStyle = WS_EX_NOPARENTNOTIFY;
	if(!(style & ES_READONLY))
		exStyle |= WS_EX_CLIENTEDGE;
	if(!pEdit->CreateEx(exStyle, _T("EDIT")
		, _T(""), style, rect->left, rect->top
		, rect->right-rect->left+1
		, rect->bottom-rect->top+1
		, parent->m_hWnd
		, menu))
	{
		delete pEdit;
		pEdit = 0;
	}
	control = pEdit;
	if (allowDD)
	{
		pEdit->m_pDropTgt = new CEBDropTarget();
		pEdit->m_pDropTgt->Register(pEdit);
		pEdit->m_pDropTgt->m_Owner = pEdit;
		pEdit->m_SpecType = specType;
	}
	return pEdit;
}

CP4EditBox * SpecControl::CreateEdit ( CWnd *parent, int code )
{
	// create a hidden edit control

	ASSERT(!control);
	this->code = code;
	CP4EditBox * pEdit = new CP4EditBox(parent);
	ASSERT( pEdit );

	if(!pEdit->CreateEx(WS_EX_CLIENTEDGE, _T("EDIT")
		, _T(""), WS_CHILD, 0, 0
		, 1
		, 1
		, parent->m_hWnd
		, NULL))
	{
		delete pEdit;
		pEdit = 0;
	}
	control = pEdit;
	return pEdit;
}

void SpecControl::AddToolTip(LPCTSTR prompt, CString const & instructionText)
{
	// See if tooltip text can be found
	tip=_T("");
	CString searchStr;
	searchStr.Format(_T("#  %s:"), prompt);
	int offset= instructionText.Find(searchStr);
	if( offset == -1)
	{
		searchStr.Format(_T("# %s:"), prompt);
		offset= instructionText.Find(searchStr);
	}
	if( offset != -1)
	{
		tip= instructionText.Mid(offset + searchStr.GetLength());
		offset=tip.Find(_T('\n'));
		if(offset != -1)
			tip=tip.Left(offset);
		tip.TrimLeft();
		tip.TrimRight();
	}
}

/*
	_________________________________________________________________
*/

void CP4SpecDlg::AddDummy()
{
	SpecControl sc;
	m_specControls.Add(sc);
}

void CP4SpecDlg::AddComboBox( const CString &prompt, const CStringArray &values
						 , const CString &editText 
						 , int specCode, BOOL readOnly
						 , int height, int width, int required
						 , const CString &indent, const CString &wCode, int lioff)
{
	int i;
	CString pmt;
	BOOL bCvt2CkhBox;
	// we stuck a 0x10 char after the prompt on all internally
	// generated fileds (that we made from a single edit line
	// with the appropriate attributes).
	if ((i = prompt.Find((TCHAR)0x10)) != -1)
	{
		pmt = prompt.Left(i) + _T(":");
		if (pmt != m_PrevCBPmt)
			m_PrevCBPmt = pmt;
		else
			pmt.Empty();
		bCvt2CkhBox = values.GetSize() == 2;
	}
	else
	{
		pmt = prompt + ((required && !readOnly) ? _T(":*") : _T(":"));
		bCvt2CkhBox = values.GetSize() == 2 
					&& (m_SpecType != P4JOB_SPEC || GET_P4REGPTR()->Cvt2ValComboToChkBx());
		m_PrevCBPmt = "";
	}

	if (lioff < 1)
		m_Y = m_LiY[m_LiY.GetCount()-1 + lioff];

	CRect rect( m_X, m_Y+3, m_X+m_StdWidth, m_Y+3 + m_StdHeight );

	if (indent == _T('M'))
	{
		rect.left += m_MaxWidth/2;
		rect.right += m_MaxWidth/2;
	}
	else if (indent == _T('I'))
	{
		rect.left += m_StdWidth + m_StdSpaceH;
		rect.right += m_StdWidth + m_StdSpaceH;
	}

	SpecControl sc;
	if (!pmt.IsEmpty())
	{
		sc.CreateLabel(this, rect, pmt);
		sc.AddToolTip(pmt, m_InstructionText);
		m_childControls.Add(CChildWindow(sc.label, CHILD_STATIC, &rect,
							TRUE, 0, wCode == _T('H'), indent == _T('M'), indent == _T('R')));
		if (!m_HasRequired)
		{
			if (pmt.Find(_T('*')) != -1)
				m_HasRequired = TRUE;
		}
	}

	if (wCode == _T("H") && (width > m_HlfWidth))
		width = m_HlfWidth;

	rect.SetRect( m_X + m_StdWidth + m_StdSpaceH
		, m_Y, m_X + m_StdWidth + width, m_Y + height );

	if (indent == _T('M'))
	{
		rect.left += m_MaxWidth/2;
		rect.right = m_X + m_MaxWidth;
	}
	else if (indent == _T('I'))
	{
		rect.left += m_StdWidth + m_StdSpaceH;
		rect.right += m_StdWidth + m_StdSpaceH;
	}

	m_LiY.Add(m_Y);

	// if we are to create a checkbox rather than a combobox
	// go do that and return;
	if (bCvt2CkhBox && GET_SERVERLEVEL() >= 11)
	{
		AddCheckBox( prompt, values, editText, specCode, 
			readOnly, height, width, required, indent, wCode, lioff, rect, sc);
		return;
	}

	// Combo's are taller than statics, so put a little more
	// whitespace between them and next lower control
	m_Y += 2 * m_StdSpaceV + height;

	DWORD style = SetBasicWinStyles( readOnly );
	style |= CBS_DROPDOWNLIST | WS_VSCROLL ;
	CComboBox * pCombo = sc.CreateCombo(this, rect, style, 
		(HMENU)IDC( m_SpecType, specCode ));
	if(!pCombo)
	{
		AddToStatus( LoadStringResource(IDS_UNABLE_TO_ADD_CHILD_WINDOW_TO_SPECIFICATION_DIALOG), 
            SV_WARNING, true );
		return;
	}
	m_childControls.Add(CChildWindow(sc.control, CHILD_DROPLIST, &rect, 
						TRUE, 0, wCode == _T('H'), indent == _T('M'), indent == _T('R')));

	//		set the selections in the combo box and
	//		pick which one will show when not pulled down.
	//		(the selection is right only if the combo box is not sorted)
	//
	BOOL newAdded = FALSE;
	if( editText == _T("new") )
	{
		pCombo->AddString( _T("new") );
		newAdded = TRUE;
	}
	CString value;
	int selected = -1;
	for ( int j = 0; j < values.GetSize ( ); j++ )
	{
		value = values.GetAt ( j );
		if ( !newAdded || (value != _T("new")) )
			pCombo->AddString( value );
		if ( value == editText )
			selected = j;
	}
	pCombo->SetCurSel( selected );

	//		okay, store the control away
	//
	m_specControls.Add(sc);

	if ( !m_pFirstControl )
		if ( !readOnly )
			m_pFirstControl = pCombo;

	if ( m_SetFocusHere )
	{
		m_SetFocusHere = FALSE;
		if ( !readOnly )
			m_pFocusControl = pCombo;
	}
}

/*
	This routine converts a 2 item ComboBox to a CheckBox 
	with the label of the 2nd item
*/

void CP4SpecDlg::AddCheckBox( const CString &prompt, const CStringArray &values
						 , const CString &editText 
						 , int specCode, BOOL readOnly
						 , int height, int width, int required
						 , const CString &indent, const CString &wCode, int lioff
						 , CRect &rect, SpecControl &sc)
{
	m_Y += m_StdSpaceV + height;

	// Adjust the width and placement of checkboxes 
	// that start in the middle of the form or at the right side
	if (indent == _T('M') || indent == _T('R'))
	{
		rect.right = rect.left + m_StdWidth + GetSystemMetrics(SM_CXVSCROLL);
		if (indent == _T('M'))
		{
			rect.left  -= m_StdWidth;
			rect.right  = rect.left + max(m_HlfWidth - GetSystemMetrics(SM_CXVSCROLL)*2,
				                          m_StdWidth + GetSystemMetrics(SM_CXVSCROLL));
		}
		else if (indent == _T('R'))
		{
			int w = rect.right - rect.left;
			rect.right = m_X + m_MaxWidth;
			rect.left  = rect.right - w;
		}
	}

	DWORD style = SetBasicWinStyles( readOnly );
	style |= BS_AUTOCHECKBOX;
	CButton * pCheck = sc.CreateCheckBox(this, rect, style, specCode, values[1]);
	if(!pCheck)
	{
		AddToStatus( LoadStringResource(IDS_UNABLE_TO_ADD_CHILD_WINDOW_TO_SPECIFICATION_DIALOG), 
            SV_WARNING, true );
		return;
	}
	m_childControls.Add(CChildWindow(sc.control, CHILD_CHECKBOX, &rect, 
						TRUE, 0, wCode == _T('H'), indent == _T('M'), indent == _T('R')));

	//		set the check in the Check box
	pCheck->SetCheck(values.GetAt(1) == editText ? BST_CHECKED : BST_UNCHECKED);

	//		set the tooltip to the "other" value
	sc.tip = values.GetAt(0);

	//		okay, store the control away
	//
	m_specControls.Add(sc);

	if ( !m_pFirstControl )
		if ( !readOnly )
			m_pFirstControl = pCheck;

	if ( m_SetFocusHere )
	{
		m_SetFocusHere = FALSE;
		if ( !readOnly )
			m_pFocusControl = pCheck;
	}
}


/*
	_________________________________________________________________
*/

void CP4SpecDlg::AddList(const CString &prompt, const CStringArray &list, int specCode, int height, 
                         int width, int scrollWidth)
{
	//		Set the position of the first control
	//
	if (m_specControls.GetSize() == 0)
		m_Y=20;

	CRect rect(m_X, m_Y, m_X+m_StdWidth, m_Y + m_StdHeight);

	SpecControl sc;
	sc.CreateLabel(this, rect, prompt + _T(":"));
	sc.AddToolTip( prompt, m_InstructionText );
	m_childControls.Add(CChildWindow(sc.label, CHILD_STATIC, &rect));

	//		Calc the rect for the list box
	//
	rect.SetRect(m_X, m_Y+m_StdHeight, m_X+width, m_Y+m_StdHeight+height); 
	if (!GET_P4REGPTR()->AllowPromptAbove())
	{
		m_Y -= m_StdHeight + m_StdSpaceV;
		rect.top -= m_StdHeight;
		height = rect.bottom - rect.top - m_StdHeight;
		rect.left += m_StdWidth + m_StdSpaceH;
		width -= m_StdWidth + m_StdSpaceH;
	}
	m_LiY.Add(m_Y);
	
	DWORD style= WS_CHILD | WS_BORDER | WS_VISIBLE | LBS_HASSTRINGS
		 | LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP;

    if( scrollWidth > width )
        style |= WS_HSCROLL;

	CReviewList *pList = sc.CreateList(this, rect, style, (HMENU) IDC(m_SpecType, specCode), specCode);
	m_childControls.Add(CChildWindow(sc.control, CHILD_CHECKLISTBOX, &rect));
	pList->SetCheckStyle(BS_AUTOCHECKBOX);

	// Because checklistboxes are an integral height
	// don't try to compute the top of the next field;
	// just get the bottom coord relative to the dlg's client area
	// and add the appropriate spaceing to that.
	CRect newRect;
	sc.control->GetWindowRect(newRect);
	ScreenToClient(newRect);
	m_Y = newRect.bottom + m_StdSpaceV;

    if( scrollWidth > width )
        // Set a sufficiently wide horizontal extent for files    
        pList->SetHorizontalExtent( scrollWidth );

	m_specControls.Add(sc);

	int index;
	int iNbrChecked = 0;
	for ( int i = 0; i < list.GetSize( ); i++ )
	{
		CString s = list.GetAt( i );

        if( !s.IsEmpty() )
        {
		    index = pList->AddString( s );

		    //		Files are selected by default, and jobs are not
		    //
			BOOL bSetCheck = FALSE;
		    if( prompt == g_tagFile )
			{
				bSetCheck = !m_SubmitOnlySelected ? TRUE
					      : m_pDeltaView->GetTreeCtrl().IsAMemeberOfSelectionList(s);
				if (bSetCheck && m_CheckOnlyChgedFiles)
					bSetCheck = (m_UnchangedFlag != 2) ? TRUE : IsFileChanged(s);
			}	
			else if ( m_CheckAllJobs )
				bSetCheck = TRUE;
			if (bSetCheck)
			{
			    pList->SetCheck( index, 1 );
				iNbrChecked++;
			}
        }
	}
	if (!iNbrChecked && (prompt == g_tagFile) && m_CheckOnlyChgedFiles)
		AfxMessageBox(IDS_SPEC_NO_FILES_CHANGED, MB_ICONEXCLAMATION);

	m_NumMultiLineChildWnds++;

	if (prompt == g_tagFile)
	{
		m_pLastFilesList = pList;
//		if (iNbrChecked == list.GetSize())
//			GetParent()->SendMessage(WM_ENABLEDISABLE, 0, FALSE);
	}
}


/*
	_________________________________________________________________
*/

BOOL CP4SpecDlg::IsFileChanged(CString filename)
{
	if (!m_pDeltaView->GetTreeCtrl().IsAMemeberOfFileList(filename))
		return TRUE;

	// for servers that support p4 revert -an chg#
	// we are done since the list is definitive
	if (m_pDeltaView->GetTreeCtrl().m_FileListDefinitive) 
		return FALSE;

	// for older servers we have to diff every file Not in the list.
	int i;
	CString fileonly = filename;
	BOOL rc = TRUE;

	m_StringList.RemoveAll();
	m_StringList.AddHead(LPCTSTR(filename));
	if ((i = fileonly.ReverseFind(_T('/'))) != -1)
		fileonly = fileonly.Mid(i+1);
	fileonly = _T("Diffing ") + fileonly;
	MainFrame()->UpdateStatus(fileonly);
	CCmd_Diff *pCmd= new CCmd_Diff;
	pCmd->Init( NULL, RUN_SYNC, HOLD_LOCK, m_pCallingCommand->GetServerKey());
	if ( pCmd->Run( &m_StringList, NULL, _T('r') ) && !pCmd->GetError() )
	{
		rc = ((pCmd->GetDiffNbrFiles() == 0) 
			|| pCmd->GetDiffErrCount()) ? TRUE : FALSE;
	}
	delete pCmd;
	return rc;
}


/*
	_________________________________________________________________
*/

void CP4SpecDlg::AddInput(const CString &prompt, const CString &editText, 
						  int specCode, BOOL readOnly, BOOL multiLine, 
						  BOOL promptAbove, int height, int width, BOOL required,
						  const CString &indent, const CString &wCode, int lioff,
						  BOOL showBrowse, BOOL allowDD)
{
	CRect browseRect;

	if (lioff < 1)
		m_Y = m_LiY[m_LiY.GetCount()-1 + lioff];

	CRect rect(m_X, m_Y, m_X + m_StdWidth, m_Y + m_StdHeight);

	if (indent == _T('M'))
	{
		rect.left += m_MaxWidth/2;
		rect.right += m_MaxWidth/2;
	}
	else if (indent == _T('I'))
	{
		rect.left += m_StdWidth + m_StdSpaceH;
		rect.right += m_StdWidth + m_StdSpaceH;
	}

	SpecControl sc;
	sc.CreateLabel(this, rect, prompt + ((required && !readOnly) ? _T(":*") : _T(":")));
	sc.AddToolTip(prompt, m_InstructionText);
	m_childControls.Add(CChildWindow(sc.label, CHILD_STATIC, &rect, 
						TRUE, 0, wCode == _T('H'), indent == _T('M'), indent == _T('R')));
	if (!m_HasRequired)
	{
		if (required && !readOnly)
			m_HasRequired = TRUE;
	}

	// Create the edit control
	int maxLines = 0;
	DWORD style = SetBasicWinStyles ( readOnly );

	if ( prompt == g_tagPassword )
		style |= ES_PASSWORD;

	if(multiLine)
	{
		if (prompt == _T("View") || prompt == _T("Reviews"))
		{
			style |= ES_MULTILINE | ES_AUTOVSCROLL | WS_HSCROLL | WS_VSCROLL | ES_WANTRETURN;
		}
		else
		{
			style |= ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_WANTRETURN;
			if (prompt == g_tagDescription)
			{
				if (m_SpecType == P4CLIENT_SPEC
				 || m_SpecType == P4LABEL_SPEC
				 || m_SpecType == P4BRANCH_SPEC)
				{
					maxLines = 3;
					int l;
					if ((l = editText.GetLength()) > 25)
					{
						int n;
						if ((n = GetNbrNL(&editText)) > 3)
							maxLines = min(m_MinLi+1, n);
						else if (l > 240)
							maxLines = min(m_MinLi, editText.GetLength()/80) + 1;
					}
				}
			}
			else if (prompt == g_tagAltRoots)
				maxLines = 2;
		}
		if (maxLines)
			height = (m_StdHeight+4) * maxLines + EXTRAHEIGHT;
		else
			m_NumMultiLineChildWnds++;
	}
    else
        style |= ES_AUTOHSCROLL;

	if (showBrowse)
	{
		CWnd *pWnd= GetDlgItem(IDC_BROWSE);
	 	pWnd->EnableWindow();
		pWnd->GetWindowRect(&browseRect);
		height = max(height, browseRect.Height());
		if (width < 0)
			width += browseRect.Width() + m_StdSpaceH;
		else
			width -= browseRect.Width() + m_StdSpaceH;
		m_BrowseFldCtrlID = m_childControls.GetCount();
	}

	if ((m_SpecType != P4CHANGE_SPEC && m_SpecType != P4JOB_SPEC && prompt == g_tagDescription)
	 || (m_SpecType == P4CLIENT_SPEC && prompt == g_tagAltRoots))
	{
		rect.SetRect(m_X + m_StdWidth + m_StdSpaceH
			, m_Y
			, m_X + width
			, m_Y + height );
		m_LiY.Add(m_Y);
		m_Y += m_StdSpaceV + height;
	}
	else if (width < 0)
	{
		rect.SetRect(m_X + m_StdWidth + m_StdSpaceH
			, m_Y
			, m_X - width			// or use this to center: , 0 - width - (m_X + m_StdWidth/2 + m_StdSpaceH)
			, m_Y + height );
		m_LiY.Add(m_Y);
		m_Y += m_StdSpaceV + height;
	}
	else if(promptAbove && GET_P4REGPTR()->AllowPromptAbove())
	{
		rect.SetRect( m_X
			, m_Y + m_StdHeight
			, m_X + width
			, m_Y + m_StdHeight + height ); 
		m_LiY.Add(m_Y);
		m_Y += m_StdHeight + m_StdSpaceV + height;
	}
	else
	{
		rect.SetRect(m_X + m_StdWidth + m_StdSpaceH
			, m_Y
			, m_X + width + (promptAbove ? 0 : m_StdWidth)
			, m_Y + height );
		m_LiY.Add(m_Y);
		m_Y += m_StdSpaceV + height;
	}
	if (indent == _T('M'))
	{
		rect.left += m_MaxWidth/2;
		rect.right = m_X + m_MaxWidth;
	}
	else if (indent == _T('I'))
	{
		rect.left += m_StdWidth + m_StdSpaceH;
		if (wCode == _T('H'))
			rect.right = rect.left + m_HlfWidth;
		else
			rect.right += m_StdWidth + m_StdSpaceH;
	}

	CP4EditBox * pEdit = sc.CreateEdit(this, rect, style, 
        (HMENU) IDC(m_SpecType, specCode), specCode, allowDD, m_SpecType);
	m_childControls.Add(CChildWindow(sc.control, 
		multiLine ? CHILD_MULTILINEEDIT : CHILD_SINGLELINEEDIT, 
		&rect, !readOnly, maxLines, wCode == _T('H'), indent == _T('M'), indent == _T('R')));

	if (m_SpecType == P4CLIENT_SPEC && prompt == g_tagAltRoots)
		pEdit->ShowScrollBar(SB_BOTH, FALSE);

	long len;
	if ((len = editText.GetLength()) > 25000)
		pEdit->SendMessage(EM_LIMITTEXT, len + 5000, 0);

	pEdit->SetWindowText(editText);
	
	// Store the spec code and control pointer
	m_specControls.Add(sc);

	if ( !m_pFirstControl )
		if ( !readOnly )
			m_pFirstControl = pEdit;

	if ( m_SetFocusHere )
	{
		m_SetFocusHere = FALSE;
		if ( !readOnly )
			m_pFocusControl = pEdit;
	}

	if (showBrowse)
	{
		m_BrowseBtnCtrlID = m_childControls.GetCount();
		browseRect.SetRect(rect.right + m_StdSpaceH, rect.top, 
						   rect.right + m_StdSpaceH + browseRect.Width(),
						   rect.top + browseRect.Height());
		GetDlgItem(IDC_BROWSE)->SetWindowPos(pEdit, browseRect.left, browseRect.top,
			browseRect.Width(), browseRect.Height(), SWP_SHOWWINDOW);
		m_childControls.Add(CChildWindow(GetDlgItem(IDC_BROWSE), CHILD_BUTTON, 
			&browseRect, TRUE, 1, FALSE, FALSE, TRUE));
		m_BrowseTag = prompt;
	}
}

/*
	_________________________________________________________________
*/

// Provide support for hiding the password edit box.  This might
// seem clumsy, but it avoids great complexity in SetControls() and
// UpdateSpec(), since those functions would otherwise have to track
// the position of the password in the specdef, skip over input 
// references at that index and decrement subsequent references

void CP4SpecDlg::AddHiddenEditBox( int i )
{
	CString tag = m_SpecData.GetTagOf( i );
	int specCode= m_SpecData.GetCodeOf( i );

	ASSERT( tag == _T("Password") );
    
	// Create a hidden label
	SpecControl sc;
	sc.CreateLabel(this);

	// Create a hidden edit control
	sc.CreateEdit(this, specCode);

	// Store the spec code and control pointer
	m_specControls.Add(sc);
}


/*
	_________________________________________________________________
*/

int CP4SpecDlg::DoCleanup() 
{
	//		Do some cleanup
	//
	for(int i = 0; i < m_specControls.GetSize(); i++)
	{
		SpecControl & sc = m_specControls[i];
		if (sc.isChkList)
		{	// this is needed because modless dialog CCheckListBoxs leak memory
			CReviewList *pList = (CReviewList *)sc.control;
			if (::IsWindow(pList->m_hWnd))
				pList->ResetContent();
		}
		delete sc.label;
		delete sc.control;
	}
	m_jobList = m_fileList = 0;
	m_childControls.RemoveAll();
	return 0;
}


BOOL CP4SpecDlg::SendSpec(LPCTSTR specText, BOOL submit/*=FALSE*/, 
						  BOOL reopen/*=FALSE*/, int unchangedFlag/*=0*/)
{
	m_Submitting= submit;

	// Set up and run SendSpec Asynchronously
	CCmd_SendSpec *pCmd= new CCmd_SendSpec;

	// 2005.1 SendSpec is no longer is HOLD_LOCK, m_pCallingCommand->GetServerKey()
	// unless this is a modal dialog. This chg was required for modless edit dialogs.
	if (m_bIsModal || (m_AutomaticallyUpdate && SERVER_BUSY()))
		pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK, m_pCallingCommand->GetServerKey() );
	else
		pCmd->Init( m_hWnd, RUN_ASYNC );
	
	m_SendingSpec=FALSE;
	if( pCmd->Run( m_SpecType, specText, submit, m_pCallingCommand->IsForceEdit(), 
						  reopen, unchangedFlag, m_pCallingCommand->IsUFlag() ) )
	{
		DisableControls();
		m_SendingSpec=TRUE;
		return TRUE;
	}
	else
	{
		delete pCmd;
		return FALSE;
	}
}

LRESULT CP4SpecDlg::OnP4SendSpec(WPARAM wParam, LPARAM lParam)
{
	// Because the spec is sent async, we could arrive here before
	// DisableControls() finishes, so wait for it to finish
	// or we will crash in DisableControls() if Destroy() gets
	// called from EndSpecDlg()'s call to PostMessage().
	while (!m_SendingSpec)
		Sleep(100);

	CCmd_SendSpec *pCmd= (CCmd_SendSpec *) wParam;
	m_SendingSpec=FALSE;
    
	if(!pCmd->GetError() && !pCmd->GetTriggerError())
	{
		m_pCallingCommand->SetNewJobName( pCmd->GetNewJobName() );
		if( pCmd->GetNewChangeNum() > 0 )
			m_pCallingCommand->SetNewChangeNum( pCmd->GetNewChangeNum() );

		if( m_SetPermPassword )
			GET_P4REGPTR()->SetP4Password( m_NewPassword, TRUE, TRUE, TRUE ) ;
		else if( m_SetTempPassword )
			GET_P4REGPTR()->SetP4Password( m_NewPassword, TRUE, FALSE, TRUE ) ;

		delete pCmd;
		if (m_bIsModal)
		{
			CDialog *pParent = (CDialog *)GetParent();
			pParent->EndDialog( m_Submitting ? IDALTERNATE : IDOK);
		}
		else
			m_pCallingCommand->EndSpecDlg( m_Submitting ? IDALTERNATE : IDOK);
		return 0;
	}
	else if(pCmd->GetTriggerError())
	{
		if (m_bIsModal)
		{
			CDialog *pParent = (CDialog *)GetParent();
			pParent->EndDialog( IDNEEDTOREFRESH );
		}
		else
			m_pCallingCommand->EndSpecDlg( IDNEEDTOREFRESH );
		EnableControls();
		delete pCmd;
	}
	else
	{
		CString txt = pCmd->GetErrorText();
		if (m_Tag == _T("Job"))
        {
            // not sure what, if anything, will result in failure to overwrite
            // existing job.  But if error says Job field is missing, don't
            // complain about failing to overwrite existing.
            if(txt.Find(_T("Missing required field 'Job'.")) != -1)
			    txt += LoadStringResource(IDS_SPEC_CANT_OVERWRITE_JOB);
        }
		if (txt.IsEmpty())
			txt = LoadStringResource(IDS_FATAL_ERROR_UNABLE_TO_SEND_SPEC);
		AfxMessageBox( txt, MB_ICONHAND);

		// Failed submit of change assumed to be an update
		if( m_Submitting && (txt.Find(_T("WSAECONNABORTED")) == -1)
						 && (txt.Find(_T("WSAECONNREFUSED")) == -1)
						 && (txt.Find(_T("WSAECONNRESET")) == -1)
						 && (txt.Find(_T("WSAETIMEDOUT")) == -1)
						 && (txt.Find(_T("WSAEHOSTUNREACH")) == -1))
		{
			if (m_bIsModal)
			{
				CDialog *pParent = (CDialog *)GetParent();
				pParent->EndDialog( IDNEEDTOREFRESH );
			}
			else
				m_pCallingCommand->EndSpecDlg( IDNEEDTOREFRESH );
		}
		else
			EnableControls();

		delete pCmd;
	}
	
	return 0;
}

void CP4SpecDlg::DisableControls()
{
	for( int i=0; i<m_childControls.GetSize(); i++ )
	{
		// this weird hack is because the CWnd* of the Browse button
		// keeps changing(!), so it must be recalc'ed each time.
		// this is a side effect of making this dialog non-modal.
		if (i == m_BrowseBtnCtrlID)
			m_childControls[i].SetWindow(GetDlgItem(IDC_BROWSE));

		CWnd *pWnd= m_childControls[i].GetWindow();
		m_childControls[i].SetVisible( pWnd->IsWindowVisible() );
		m_childControls[i].SetEnabled( pWnd->IsWindowEnabled() );
		pWnd->ModifyStyle( WS_VISIBLE, WS_DISABLED, 0);
	}
	GetParent()->SendMessage(WM_MODIFYSTYLE, WS_VISIBLE, WS_DISABLED);

	CRect rectWindow;
	CRect rectClient;
	GetParent()->GetWindowRect( &rectWindow );
	GetClientRect( &rectClient );

	CRect rect= CRect( 0, 0, 450, 150 );
	if( m_Submitting )
		m_BusyMessage.SetWindowText(LoadStringResource(IDS_SUBMITTING_CHANGE_PLEASE_WAIT));
	else
		m_BusyMessage.SetWindowText(LoadStringResource(IDS_SENDING_SPECIFICATION_PLEASE_WAIT));

	m_BusyMessage.ModifyStyle( 0, WS_VISIBLE, 0 );
	::SendMessage(m_BusyMessage.m_hWnd, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FONT), FALSE);
	m_BusyMessage.MoveWindow( &rect );

	CRect rectCancel;
	CWnd *pWnd= GetDlgItem(ID_FILE_CANCEL);
	pWnd->GetWindowRect(&rectCancel);
	rect.InflateRect(0, 0, rectWindow.Width()- rectClient.Width(), 
						   rectWindow.Height()- rectClient.Height() + rectCancel.Height());
	GetParent()->MoveWindow( &rect );
	GetParent()->CenterWindow();
	GetParent()->RedrawWindow();

	SetScrollRange(SB_VERT, 0, 0, TRUE);
	ShowScrollBar(SB_VERT, FALSE);

	// turn on and position the Cancel button
	pWnd->ShowWindow( SW_SHOWNORMAL );
	pWnd->EnableWindow();
	GetClientRect(&rect);
	rect.top    = rect.bottom - rectCancel.Height();
	rect.left   = (rect.Width() - rectCancel.Width())/2;
	rect.right  = rect.left + rectCancel.Width();
	pWnd->MoveWindow(&rect);
}


void CP4SpecDlg::EnableControls()
{
	// hide the 2 controls related to sending a spec
	GetDlgItem(ID_FILE_CANCEL)->ModifyStyle( WS_VISIBLE, WS_DISABLED, 0);
	m_BusyMessage.ShowWindow( SW_HIDE );

	// un-hide the other controls
	for( int i=0; i<m_childControls.GetSize(); i++ )
	{
		// this weird hack is because the CWnd* of the Browse button
		// keeps changing(!), so it must be recalc'ed each time.
		// this is a side effect of making this dialog non-modal.
		if (i == m_BrowseBtnCtrlID)
			m_childControls[i].SetWindow(GetDlgItem(IDC_BROWSE));

		CWnd *pWnd= m_childControls[i].GetWindow();
		if( m_childControls[i].GetVisible() )
			pWnd->ShowWindow( SW_SHOWNORMAL );
		if( m_childControls[i].GetEnabled() )
			pWnd->EnableWindow();
	}
	GetParent()->SendMessage(WM_MODIFYSTYLE, WS_DISABLED, WS_VISIBLE);

	if (!m_AutomaticallyUpdate)
		m_WinPos.RestoreWindowPosition();
	RedrawWindow();
}

// The user pressed the Enter key and we got a button click for IDOK
void CP4SpecDlg::OnEnter() 
{
	if (m_AllowSubmit)
		OnAlternate();
	else
		On_OK();
}

void CP4SpecDlg::On_OK() 
{
	if (!m_AutomaticallyUpdate)
		m_WinPos.SaveWindowPosition();

	if( UpdateSpec() )
	{
		SendSpec( m_NewForm, FALSE );
	}
}

void CP4SpecDlg::OnAlternate() 
{
	if (m_AllowSubmit)
	{
		if( m_fileList )
		{
			CString value;
			BOOL bChecked = FALSE;
			int count = m_fileList->GetCount();
			for(int i=0; i<count; i++)
			{
				m_fileList->GetText(i, value);
				if(!value.IsEmpty() && m_fileList->GetCheck(i)==1)
				{
					bChecked = TRUE;
					break;
				}
			}
			if (!bChecked)
			{
				AfxMessageBox(IDS_SPEC_EMPTY_SUBMIT, MB_ICONEXCLAMATION);
				return;
			}
		}
		if (!m_AutomaticallyUpdate)
			m_WinPos.SaveWindowPosition();
		if( UpdateSpec() )
		{
			SendSpec( m_NewForm, TRUE, GET_SERVERLEVEL() >= 13 ? m_bReopen : FALSE, 
									   GET_SERVERLEVEL() >= 21 ? m_UnchangedFlag : 0 );
		}
	}
	else MessageBeep(0);
}

void CP4SpecDlg::On_Cancel() 
{
	if (!m_AutomaticallyUpdate)
		m_WinPos.SaveWindowPosition();
	if (m_bIsModal)
	{
		CDialog *pParent = (CDialog *)GetParent();
		pParent->EndDialog(IDCANCEL);
	}
	else
		m_pCallingCommand->EndSpecDlg(IDCANCEL);
}

void CP4SpecDlg::OnCancel() 
{
	// Eat ESC while sending spec, so the server's reply can
	// be properly processed.  Don't confuse this with On_Cancel(),
	// which is called when the cancel button is hit.
	if( ! m_SendingSpec )	
	{
		if (m_ChangesHaveBeenMade)	// Have changes been made? This is our best guess...
		{
			// Warn that they are about to discard changes
			BOOL b;
			if ((b = GET_P4REGPTR()->DontShowDiscardFormChgs()) == FALSE)
			{
				if (MsgBox(IDS_DISCARD_CHGS, MB_ICONQUESTION | MB_DEFBUTTON2, 
							0, this, &b) == IDC_BUTTON2)
					return;
				if (b)
					GET_P4REGPTR()->SetDontShowDiscardFormChgs(b);
			}
		}
		// Cancel the dialog
		if (!m_AutomaticallyUpdate)
			m_WinPos.SaveWindowPosition();
		if (m_bIsModal)
		{
			CDialog *pParent = (CDialog *)GetParent();
			pParent->EndDialog(IDCANCEL);
		}
		else
			m_pCallingCommand->EndSpecDlg(IDCANCEL);
	}
}


/*
	_________________________________________________________________
*/

int CP4SpecDlg::WordCount( const CString& cst, int type )
{
	CTokenString tstr;

	tstr.Create(cst);
	int count=0;
	while( lstrlen( tstr.GetToken(TRUE)) > 0)
		count++;

	return count ;
}


/*
	_________________________________________________________________
*/

BOOL CP4SpecDlg::CheckNumWords ( const CString tag, const CString &cst
						, int type , int words, int required, CString &msg )
{
	msg.Empty ( );

	if ( !required && cst.IsEmpty( ) )
		return TRUE;
	
	int count = words;
	if ( type == SDT_WORD )
	{
		count = WordCount( cst, type );
		
		if( count > words )
		{
            msg.FormatMessage( words > 1 ? IDS_FIELD_s_CAN_HAVE_ONLY_n_WORDS : IDS_FIELD_s_CAN_HAVE_ONLY_ONE_WORD, 
				tag, words);
			return FALSE;
		}
		else if( count < words && ( count > 0 || required != 0))
		{
            msg.FormatMessage( words > 1 ? IDS_FIELD_s_NEEDS_n_WORDS : IDS_FIELD_s_NEEDS_ONE_WORD, 
				tag , words);
			return FALSE;
		}
	}
	else if( type == SDT_WLIST )
	{
		CString r = cst;
		while ( !r.IsEmpty( ) )
		{
			int len = r.Find ( g_CRLF );
            CString l;
            if(len == -1)
            {
                l = r;
                r.Empty();
            }
            else if(len == 0)
            {
                r = r.Mid(2);
                continue;		// found a blank line, go try again
            }
            else
            {
                l = r.Left(len);
                r = r.Mid(len+2);
            }

			count = WordCount( l, type );
		
			if( count > words )
			{
                msg.FormatMessage( words > 1 ? IDS_FIELD_s_CAN_HAVE_ONLY_n_WORDS_PER_LINE : IDS_FIELD_s_CAN_HAVE_ONLY_ONE_WORD_PER_LINE, 
    				tag, words);
				return FALSE;
			}
			else if( count < words && ( count > 0 || required != 0))
			{
                msg.FormatMessage( words > 1 ? IDS_FIELD_s_NEEDS_n_WORDS_PER_LINE : IDS_FIELD_s_NEEDS_ONE_WORD_PER_LINE, 
    				tag, words);
				return FALSE;
			}
		}
	}

	return count == words;
}

/*
	_________________________________________________________________

	Calculate the width of the widest prompt in pixels
	_________________________________________________________________
*/

int CP4SpecDlg::GetLengthLongestPrompt()
{
	CString longest, tag;
	int mx = 0;
    for( int i = 0; i < m_SpecData.GetNumItems( ); i++ ) 
	{
		tag = m_SpecData.GetTagOf( i );
		int lgth = tag.GetLength();
		if (mx < lgth)
		{
			mx = lgth;
			longest = tag;
		}
	}

	CDC *pDC = GetDC();
	CFont *pOldFont= pDC->SelectObject( GetFont() );
	pDC->SelectObject( &m_Font );
	CSize sz = pDC->GetTextExtent(longest + _T(":*"));
	pDC->SelectObject( pOldFont );
	ReleaseDC( pDC );

	return sz.cx;
}


/*
	_________________________________________________________________
*/

CString CP4SpecDlg::GetDialogueCaption ( )
{
    CString types = LoadStringResource(IDS_SPECCAPTION_TYPES);
    for(int i = 0; i < m_SpecType; i++)
        types = types.Mid(types.Find(_T('|'))+1);
    types = types.Left(types.Find(_T('|')));

    CString winCaption;
	winCaption.FormatMessage( IDS_SPECCAPTION_s, types );
	return winCaption;
}


/*
	_________________________________________________________________
*/

BOOL CP4SpecDlg::SetControls() 
{
	ASSERT(m_specControls.GetSize() == 0);
	ASSERT( m_childControls.GetSize() <= 6 );
	ASSERT( m_NumMultiLineChildWnds == 0 );

	int code = -1, type;
	CString value, tag;
	BOOL isReadOnly;
	CStringArray aPresets;
	BOOL bJobsDone = FALSE;

	m_OrigPassword.Empty();
	m_ReadOrigPassword= FALSE;

	m_OrigRoot.Empty();
	m_OrigView.Empty();
	m_ReadOrigRoot = m_ReadOrigView = m_SyncAfter = FALSE;

	RECT rectPos;
	m_ReqStatic.GetWindowRect(&rectPos);
	ScreenToClient(&rectPos);
	int w = rectPos.right - rectPos.left;
	rectPos.right = m_X + m_MaxWidth;
	rectPos.left  = rectPos.right - w;
	m_ReqStatic.MoveWindow(&rectPos);
	m_childControls.Add(CChildWindow(&m_ReqStatic, CHILD_RIGHTSTATIC, &rectPos));
	m_Y = rectPos.bottom + 1;

    for( int i = 0; i < m_SpecData.GetNumItems( ); i++ ) 
	{	
		//		put m_Specdata stuff for this item in local variables
		//		to prevent code litter. manipulate these variables
		//		and then before the call to Add() copy them into the spec data
		//
		code = m_SpecData.GetCodeOf( i );
		type = m_SpecData.GetTypeOf( i );
		isReadOnly = m_SpecData.GetReadOnlyOf( i );
		tag = m_SpecData.GetTagOf( i );
		value = m_SpecData.GetValueOf ( i );

		switch ( type )
		{
			//		for a block of text or a single line of text, strip the tabs
			//
			case SDT_TEXT:
				if (GET_P4REGPTR()->PreserveSpecFormat( ))
				{
					value = MakeCRs( value );
					break;
				}
			case SDT_BULK:	
			case SDT_LINE:
			case SDT_DATE:
				value = RemoveTabs( value );
				break;

			//		for multiple lines, wait until loop ends
			//		(job list for changelist, user reviews list, and views )
			//
			case SDT_WLIST:	
			case SDT_LLIST:	
				if ( tag == g_tagAltRoots )
				{
					// for multiple client roots, we will have both a line type
					// element called "Root" and a llist type element called 
					// "AltRoots".  Create a view control for the AltRoots info.
					CWnd * pEdit = m_pFocusControl;
					AddView( g_tagAltRoots, m_SpecData.m_aList );
					m_pFocusControl = pEdit;
					for (int j = -1; ++j < m_SpecData.m_aList.GetSize(); )
					{
						if (!m_OrigRoot.IsEmpty())
							m_OrigRoot += _T('\r');
						m_OrigRoot += m_SpecData.m_aList.GetAt(j);
					}
					m_ReadOrigRoot= TRUE;
				}
				continue;

			case SDT_WORD:	// single line, N words
				break;

			case SDT_SELECT:
				GetComboBoxValues( value, aPresets );
				break;

			default:
				break;
		}

		// Keep track of original password
		if( tag == _T("Password") )
		{
			if( value.Find(_T("******")) == 0 )
				value= GET_P4REGPTR()->GetP4UserPassword();

			m_OrigPassword= value;
			m_ReadOrigPassword= TRUE;
		}
		// Keep track of original root
		else if( tag == g_tagRoot )
		{
			if (!m_Root2Use.IsEmpty())
				value = m_Root2Use;
			m_OrigRoot= value;
			m_ReadOrigRoot= TRUE;
		}

		//		user is creating a job, label, branch, etc.
		//		make the value blank so user can fill it in.
		//
		if( value == NEWSPECNAME )
		{
			value.Empty( );
			isReadOnly = FALSE; 
		}

		//		Job complications:
		//
		//		1.	The server allows any job to be changed, but that screws
		//			up the gui's refresh of the job window, because it  
		//			copies rather than renames the job
		//
		if( m_SpecType == P4JOB_SPEC && tag == g_tagJob )
			if( value !=  _T("new") )
				isReadOnly = TRUE; 

		//		2.	if a job's status is 'new", the status should be read only 
		//
		if( m_SpecType == P4JOB_SPEC ) 
			if ( tag == g_tagStatus )
				if( value == _T("new") )
					isReadOnly = TRUE; 

		//		Changelist complication:
		//
		//		The Job Status should go after the jobs
		//
		if( m_SpecType == P4CHANGE_SPEC && tag == g_tagJobStatus )
		{
			if ( m_SpecData.m_aJobs.GetSize ( ) )
			{
				if ( ! m_SpecData.m_aJobs.GetAt( 0 ).IsEmpty( ) )
				{
					AddList( _T("Jobs"), m_SpecData.m_aJobs, code, 3 * m_StdHeight, m_MaxWidth, m_MaxWidth );
					m_jobList = dynamic_cast<CReviewList*>(m_specControls[m_specControls.GetUpperBound()].control);
				}
			}
			bJobsDone = TRUE;
			// Add some backwards compatible spec info
			if( GET_SERVERLEVEL() < 16 && GET_SERVERLEVEL() > 7 )
			{
				m_SpecData.SetIndentOf( i, _T("I") );
				m_SpecData.SetwCodeOf ( i, _T("H") );
			}
		}

		//		okay, we've manipulated all we can. set any spec data value
		//		that's changed.
		//
		m_SpecData.SetValueOf ( i, value );
		m_SpecData.SetIsReadOnlyOf( i, isReadOnly );

		//		okay, add either an edit field or a combo box to the dialogue
		//
		if( ( GET_SERVERLEVEL() >= 6 &&  tag == _T("Password") ) )
			AddHiddenEditBox( i );
		else if ( type == SDT_SELECT )
		{
			AddComboBox( tag, aPresets, m_SpecData.GetValueOf( i ), code , isReadOnly, 
						  m_StdHeight , m_ComboWidth, m_SpecData.GetRequiredOf( i ), 
						  m_SpecData.GetIndentOf( i ), m_SpecData.GetwCodeOf( i ),
						  m_SpecData.GetLiOffsetOf( i )); 
			aPresets.RemoveAll( );
		}
		else
			AddEditBox( i );
	}

	//		now add any views, job lists or file lists
	//
	if ( m_SpecData.m_aReview.GetSize ( ) )
		AddView( g_tagReviews, m_SpecData.m_aReview, m_SpecType == P4USER_SPEC );

	if ( m_SpecData.m_aWordList.GetSize( ) )
	{
		BOOL b = m_SpecType == P4LABEL_SPEC || m_SpecType == P4CLIENT_SPEC;
		AddView( g_tagView, m_SpecData.m_aWordList, b );
		CString e;
		for ( int i = 0; i < m_SpecData.m_aWordList.GetSize( ); i++ )
		{
			CObject *o = m_SpecData.m_aWordList.GetAt( i );
			CStringArray *sa = ( CStringArray * )o;
			for( int j = 0; j < sa->GetSize( ) ; j++ )
			{
				e = sa->GetAt( j );
				m_OrigView += e + _T(" ");
			}
			m_OrigView += g_CRLF;
		}
		m_ReadOrigView = TRUE;
	}

	if ( m_SpecData.m_aJobs.GetSize ( ) && !bJobsDone )
	{
		if ( ! m_SpecData.m_aJobs.GetAt( 0 ).IsEmpty( ) )
		{
			AddList( _T("Jobs"), m_SpecData.m_aJobs, code, 3 * m_StdHeight, m_MaxWidth, m_MaxWidth );
			m_jobList = dynamic_cast<CReviewList*>(m_specControls[m_specControls.GetUpperBound()].control);
		}
	}
	if ( m_SpecData.m_aFile.GetSize ( ) && m_AddFilesControl )
	{
		if ( ! m_SpecData.m_aFile.GetAt( 0 ).IsEmpty( ) )
		{
            ASSERT(code > -1);
			AddList( g_tagFile, m_SpecData.m_aFile, code, 6 * m_StdHeight, m_MaxWidth, m_MaxWidth*3 );
			m_fileList = dynamic_cast<CReviewList*>(m_specControls[m_specControls.GetUpperBound()].control);
			MainFrame()->UpdateStatus(_T(" "));
		}
	}
	else m_SpecData.m_aFile.RemoveAll( );

	if (!m_HasRequired)
		m_ReqStatic.ShowWindow(SW_HIDE);

	return TRUE;
}


/*
	_________________________________________________________________

	all possible values and the actual value for this field are in 
	one cstring ( e.g., "SIR:BUG/SIR/unknown" )
	put the real value, which is to the left of the colon, into value.
	put all possible values, to the right of the colon and separated
	by slashes, into a string array for the combo box. 
	_________________________________________________________________
*/

void CP4SpecDlg::GetComboBoxValues( CString &value, CStringArray &aPresets )
{
	int where = value.Find( g_SelectionSeparator );
	if ( where > -1 )
	{
		CString presets = value.Right( value.GetLength( ) 
			- where - g_SelectionSeparator.GetLength( ) );
		CString pre = presets;
		value = value.Left ( where );
		while ( pre.GetLength( ) )
		{
			int sep = presets.Find( g_PresetSeparator );
			if(sep > -1)
			{
				pre = presets.Left( sep );
				aPresets.Add( pre );
			}
			else
			{
				pre.Empty();
				aPresets.Add( presets );
			}
			presets = presets.Right ( presets.GetLength( ) - (sep + 1));
		}
	}
}


/*
	_________________________________________________________________
*/

void CP4SpecDlg::AddEditBox( int i )
{
	int code = m_SpecData.GetCodeOf( i );
	int type = m_SpecData.GetTypeOf( i );
	CString tag = m_SpecData.GetTagOf( i );
	BOOL isReadOnly = m_SpecData.GetReadOnlyOf( i );
	BOOL showBrowse = FALSE;

	BOOL bMultiLine = TRUE; 
	int height = m_StdHeight;
	int width = m_MaxWidth;
	int maxlen = m_SpecData.GetSizeOf( i );

	int lioff = m_SpecData.GetLiOffsetOf( i );
	CString indent = m_SpecData.GetIndentOf( i );
	CString wCode = m_SpecData.GetwCodeOf( i );

	// If the 1st field on the spec is read-only
	// widen it to the full width of the form
	// (only necessary for newer servers tho,
	// older servers serve fullwidth name fields)
	if (!i && isReadOnly && (GET_SERVERLEVEL() >= 16) && (m_SpecType != P4JOB_SPEC))
	{
		wCode = _T("R");
	}
	// Add some backwards compatible spec info
    else if( GET_SERVERLEVEL() < 16 && GET_SERVERLEVEL() > 7 )
    {
		switch(m_SpecType)
        {
		case P4CHANGE_SPEC:
			if (i >= 1 && i <= 4)
			{
				lioff = (i == 2 || i == 4) ? 0 : 1;
				indent = (i == 1 || i == 4) ? _T("M") : _T("B");
				wCode = _T("H");
			}
			break;
        case P4CLIENT_SPEC:
			if (i >= 1 && i <= 4)
			{
				lioff = i == 3 ? -1 : 1;
				indent = i > 2 ? _T("M") : _T("B");
				wCode = _T("H");
			}
			else if (tag == _T("Root") && !m_BrowseShown)
				showBrowse = m_BrowseShown = TRUE;
			break;
        case P4BRANCH_SPEC:
        case P4LABEL_SPEC:
			if (i >= 1 && i <= 3)
			{
				lioff  = i == 3 ? 0 : 1;
				indent = i == 3 ? _T("M") : _T("B");
				wCode = _T("H");
			}
            break;
        case P4USER_SPEC:
			if (i >= 1 && i <= 4)
			{
				wCode = _T("H");
				if ((i == 1) || (i == 4))
				{
					indent = _T("M");
					lioff = (i == 1) ? 1 : 0;
				}
				else if (i == 2)
					lioff = 0;
			}
            break;
        }

    }
	if ( type == SDT_WLIST  || type == SDT_LLIST
		|| ( type == SDT_TEXT &&  maxlen == 0  ) )
	{
		height = 4 * m_StdHeight + EXTRAHEIGHT;
	}
	else if ( type == SDT_TEXT && maxlen > 63  )
	{
		m_MinLi = m_SpecData.GetNumItems( ) > 16 ? m_MinLi : max(m_MinLi, 4);
		height = EXTRAHEIGHT + max( m_MinLi * m_StdHeight,  ( ( maxlen + 63 ) / 64 ) * m_StdHeight );
	}
	else
	{
		width = ((tag == "Root") || (tag == _T("Options"))) ? -1 * m_MaxWidth : m_ComboWidth;
		bMultiLine = FALSE;
	}
	if (wCode.GetAt(0) == _T('H') && (width > m_HlfWidth))
		width = m_HlfWidth;
		
	//		if we have a multiline field, put the tag above the
	//		field instead of along side it.
	//
	BOOL tagAboveValue = bMultiLine;

	// This is temp until the server is smarter
	// should be if( GET_SERVERLEVEL() == 16 && 
	if (tag == _T("Root") && m_SpecType == P4CLIENT_SPEC && !m_BrowseShown)
		showBrowse = m_BrowseShown = TRUE;

	AddInput( tag 
		, m_SpecData.GetValueOf( i )
		, code, isReadOnly
		, bMultiLine, tagAboveValue
		, height, width, m_SpecData.GetRequiredOf ( i )
		, indent, wCode, lioff, showBrowse );
}


/*
	_________________________________________________________________

	string all views together into one string, separate with carriage
	return-line feed, and put into dialogue
	_________________________________________________________________
*/

void CP4SpecDlg::AddView( const CString &tag, const CObArray &oa, BOOL allowDD/*=FALSE*/ )
{
	CString s;
	CString e;

	for ( int i = 0 ; i < oa.GetSize ( ) ; i++ )
	{
		CObject *o = oa.GetAt( i );
		CStringArray *sa = ( CStringArray * )o;
		for( int j = 0; j < sa->GetSize( ) ; j++ )
		{
			e = sa->GetAt( j );
			if (e != _T("@"))		// single @ means put a blank line here
				s += e + _T(" ");
			else
				m_SetFocusHere = TRUE;
		}
		s += g_CRLF;
	}

	AddInput( tag 
			, s 
			, 0		//fanny: what do i need this for?
			, FALSE //		is read only
			, TRUE	//		all views are multiline
			, TRUE	//		tag is above the value, not to the side
			, 4 * m_StdHeight 
			, m_MaxWidth
			, FALSE,_T("B"),_T("A"),1,FALSE,allowDD
			);
}


/*
	_________________________________________________________________
*/

void CP4SpecDlg::AddView( const CString &tag, const CStringArray &sa, BOOL allowDD/*=FALSE*/ )
{
	CString s;
	CString e;
	for ( int i = 0; i < sa.GetSize( ); i++ )
	{
		e = sa.GetAt( i );
		if (e.IsEmpty())
			m_SetFocusHere = TRUE;
		s += e + g_CRLF;
	}

	AddInput( tag 
			, s 
			, 0		//fanny: what do i need this for?
			, FALSE //		is read only
			, TRUE	//		all views are multiline
			, TRUE	//		tag is above the value, not to the side
			, 4 * m_StdHeight 
			, m_MaxWidth
			, FALSE,_T("B"),_T("A"),1,FALSE,allowDD
			);
}


/*
	_________________________________________________________________
*/

// A replacement for CString::Remove that overcomes a bug in the original
// when operating on MBCS strings.
void CStringRemove(CString &str, TCHAR chRemove)
{
	SET_BUSYCURSOR();
	// if the character to be removed is less than a space
	// we can safely use the native (and much faster!) remove
	if ((unsigned)chRemove < (unsigned)_T(' '))
	{
		str.Remove(chRemove);
	}
	else
	{
		for(int pos = 0; (pos = str.Find(chRemove, pos)) != -1; )
		{
			str.Delete(pos);
			if(pos)
				pos--;
		}
    }
}

BOOL CP4SpecDlg::UpdateSpec( )
{
	CString value;
	CString tag;
	int type;
	int words;
	int required;
	CString txt;
	CString newRoot = _T("");
	CString newView = _T(":");
	CWnd *pControl;

	m_SetPermPassword= FALSE;
	m_SetTempPassword= FALSE;
	m_NewPassword= m_OrigPassword;
	m_Tag.Empty();

	int i;
	for( i = 0; i< m_specControls.GetSize(); i++ ) 
	{
		pControl = m_specControls[ i ].control;
		// skip over any dummy controls
		if(!pControl)
			continue;
		value = GetNewValue( pControl, i );
		tag = m_SpecData.GetTagOf( i );
		type = m_SpecData.GetTypeOf( i );
		words = m_SpecData.GetNWordsOf ( i );
		required = m_SpecData.GetRequiredOf ( i );

		//  strip out any leading or trailing whitespace
		//	unless this is a TEXT field and we are to preserve
		//	the whitespace.
		if ((type != SDT_TEXT) || !GET_P4REGPTR()->PreserveSpecFormat( ))
		{
			value.TrimRight();
			value.TrimLeft();
			if (type == SDT_TEXT)
//				value.Remove(_T('\r'));
                CStringRemove(value, _T('\r'));
            else if(type != SDT_BULK)
            {
                // if value is quoted, make sure there's something inside the quotes
                // otherwise, it's the same as empty
                if(value == "\"" || value == "\"\"")
                    value.Empty();

				if (type != SDT_WORD)
				{
					// anything following a '#' will be considered a comment and stripped
					// so do it here to allow better error detection and handling
					int comment = value.Find(_T('#'));
					if(comment != -1)
					{
						if (type == SDT_LINE)
						{
							value.TrimLeft(_T('\"'));
							value.TrimRight(_T('\"'));
							value = _T('\"') + value + _T('\"');
						}
						else
						{
							CString errTxt;
							errTxt.FormatMessage(IDS_NO_POUND_ALLOWED_s_s, tag, value);
							return ShowUserError( errTxt, pControl );
						}
					}
				}
            }
		}

		//		Verify that required field is not empty, that it has
		//		the number of words it's supposed to have, and that
		//		if it's the description, it doesn't have the garbage
		//		field still in it
		//
		if ( m_SpecType == P4JOB_SPEC && tag == _T("Job") )
		{
			value.TrimRight(_T(" \""));
			value.TrimLeft (_T(" \""));
		}
		if ( value.IsEmpty( ) && required )
        {
            CString errTxt;
            errTxt.FormatMessage(IDS_PLEASE_ENTER_A_VALUE_FOR_s, tag);
			return ShowUserError( errTxt, pControl );
        }

		// For the Client View the following is not considered valid:
		// -"//depot/two words/... //myclient/two words/...
		// because the - needs to be inside the quotes for CheckNumWords().
		// So replace all /n-" with /n"-
		if ( type == SDT_WLIST && m_SpecType == P4CLIENT_SPEC && tag == _T("View") )
			value.Replace(_T("\n-\""), _T("\n\"-"));

		if ( !CheckNumWords ( tag, value, type , words, required, txt ) )
			return ShowUserError( txt, pControl );

		if ( value == CCmd_EditSpec::g_blankDesc )
			return ShowUserError( LoadStringResource(IDS_PLEASE_ENTER_A_DESCRIPTION), pControl );

		if ( tag == LoadStringResource(IDS_DESCRIPTION) )
		{
			//		Job description needs to have '\r' chars removed and possibly be wrapped.
			//
			if ( m_SpecType == P4JOB_SPEC )
			{
				if (GET_P4REGPTR()->GetDescWrapSw())
					value = WrapDesc(value, GET_P4REGPTR()->GetDescWrap());
				value = UnMakeCRs( value );
			}
			//		Change descriptions might need to be wrapped
			//
			else if ( m_SpecType == P4CHANGE_SPEC )
			{
				if (GET_P4REGPTR()->GetDescWrapSw())
					value = WrapDesc(value, GET_P4REGPTR()->GetDescWrap());
				m_NewChangeDesc = value;
				m_pCallingCommand->SetChangeDesc(value);
			}
		}
		if( (GET_SERVERLEVEL() >= 6) && (tag == _T("Password")) )
		{
			if( GET_SERVERLEVEL() >= 18)
				value = _T("******");
			// else just ignore the hidden edit field, since value didnt change
		}
		else if ( tag == _T("Password") && value != m_OrigPassword )
		{
			if( AfxMessageBox( IDS_YOU_HAVE_CHANGED_YOUR_USER_PASSWORD, MB_YESNO|MB_ICONQUESTION) == IDYES )
				m_SetPermPassword= TRUE;
			else
				m_SetTempPassword= TRUE;
			
			m_NewPassword= value;
		}

		// check to see it the label name they typed is the same as an existing label name
		// issue warning if they said they were intending to create a new label (rather than
		// intending to update an existing label)
		if( ( ( tag == _T("Label") ) 
			&& m_pCallingCommand->GetIsRequestingNew()
			&& (((CLabelView *)(m_pCallingCommand->GetCaller()))->GetListCtrl().FindInList(value) != -1) )
		 || ( ( tag == _T("Branch") ) 
			&& m_pCallingCommand->GetIsRequestingNew()
			&& (((CBranchView *)(m_pCallingCommand->GetCaller()))->GetListCtrl().FindInList(value) != -1) )
		 || ( ( tag == _T("Client") ) 
			&& m_pCallingCommand->GetIsRequestingNew()
			&& (((CClientView *)(m_pCallingCommand->GetCaller()))->GetListCtrl().FindInListAll(value) != -1) ) 
		 || ( ( tag == _T("User") ) 
			&& m_pCallingCommand->GetIsRequestingNew()
			&& (((CUserView *)(m_pCallingCommand->GetCaller()))->GetListCtrl().FindInList(value) != -1) ) )
		{
			CString txt;
			txt.FormatMessage(IDS_s_s_ALREADY_EXISTS_CONTINUING_WILL_OVERWRITE_s, tag, value, value);
			if(AfxMessageBox(txt, MB_YESNO|MB_ICONEXCLAMATION) != IDYES)
				return FALSE;
		}
		else if ( tag == _T("Job") )
		{
			if (value.GetAt(0) == _T('-'))
			{
				CString txt;
				txt.FormatMessage(IDS_SPEC_INVALID_JOBNAME_s, value);
				AfxMessageBox(txt, MB_ICONEXCLAMATION);
				return FALSE;
			}
			if ( m_pCallingCommand->GetIsRequestingNew() )
			{
				if (((CJobView *)(m_pCallingCommand->GetCaller()))->GetListCtrl().FindInList(value) != -1)
				{
					CString txt;
					txt.FormatMessage(IDS_SPEC_s_s_ALREADY_EXISTS_CANT_OVERWRITE_s_WITH_s, tag, value, value, tag);
					AfxMessageBox(txt, MB_ICONEXCLAMATION);
					return FALSE;
				}
				else m_Tag = tag;
			}
		}

		if ( m_SpecType == P4CLIENT_SPEC && tag == _T("Root") )
		{
			newRoot = value + _T('\r');
			m_SpecData.SetValueOf( i , value ) ;					 //		set the new value
		}
		else if( tag == _T("Reviews") )
			m_SpecData.UpdateReviews( value );
		else if ( tag == _T("Jobs") || tag == _T("Files") )
			;//they are taken care of below
		else if ( type == SDT_LLIST )
			m_SpecData.UpdateLList( m_SpecData.m_aList, value );
		else if ( type == SDT_WLIST ) //views and a possible user defined one
		{
			// remove any occurances of multiple blank lines since the server will reject them
			if ( tag == _T("View") )
				while (value.Replace(_T("\r\n\r\n"), _T("\r\n")))
					;
			m_SpecData.UpdateWordList( words, value );
			if ( m_SpecType == P4CLIENT_SPEC && tag == _T("View") )
				newView = value;
		}
		else if( !( tag == _T("Password"
				 && GET_SERVERLEVEL() < 18 
				 && GET_SERVERLEVEL() >= 6 ) ) ) // if no need to ignore the hidden password field
			m_SpecData.SetValueOf( i , value ) ;					 //		set the new value
	}

	// If this is an existing client and they've changed the Root, issue warning
	if (m_SpecType == P4CLIENT_SPEC && !newRoot.IsEmpty())
	{
		ASSERT(m_ReadOrigRoot);
		int n;
		if ((n = m_SpecData.m_aList.GetSize()) > 0)
		{
			for (int j = -1; ++j < n; )
				newRoot += m_SpecData.m_aList.GetAt(j);
		}
		newRoot.TrimRight();
		m_OrigRoot.TrimRight();
#ifdef _DEBUG
		CString msg;
		msg.Format( _T("Old Root=[%s]"),  m_OrigRoot);
		AddToStatus( msg, SV_DEBUG );
		msg.Format( _T("New Root=[%s]"),  newRoot);
		AddToStatus( msg, SV_DEBUG );
		msg.Format( _T("IsANewClient=[%d]"),  m_pCallingCommand->GetIsNewClient());
		AddToStatus( msg, SV_DEBUG );
#endif
		if (newRoot.CompareNoCase(m_OrigRoot) && !m_pCallingCommand->GetIsNewClient())
		{
			if (IDYES != AfxMessageBox(IDS_YOU_HAVE_CHANGED_THE_ROOT_FOR_THIS_CLIENT_YOU_MUST,
								MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION))
				return FALSE;
		}
	}

	// Have they changed the client View?
	if (m_SpecType == P4CLIENT_SPEC && !newView != CString(_T(":")))
	{
		ASSERT(m_ReadOrigView);
		newView.TrimRight();
		m_OrigView.TrimRight();
#ifdef _DEBUG
		CString msg;
		msg.Format( _T("Old View=[%s]"),  m_OrigView);
		AddToStatus( msg, SV_DEBUG );
		msg.Format( _T("New View=[%s]"),  newView);
		AddToStatus( msg, SV_DEBUG );
		msg.Format( _T("IsANewClient=[%d]"),  m_pCallingCommand->GetIsNewClient());
		AddToStatus( msg, SV_DEBUG );
#endif
		if (newView != m_OrigView)
			m_SyncAfter = TRUE;
	}

	// For numbered changelists, if any job or file is unchecked, then
	// set flag so we know to refresh the pending changelists pane
	m_EditedLists= FALSE;
 
	// Add jobs
	int count= 0;
	m_SpecData.m_aJobs.RemoveAll ( );
	if( m_jobList )
	{
		count = m_jobList->GetCount( );
		
		for( i = 0; i< count; i++ )
		{
			m_jobList->GetText( i, value );
			if( value != "" && m_jobList->GetCheck( i ) == 1 )
			{
				// Jobs are supposed to be one word
				// but we may have fiddled with the spec
				// in order to get the description to show; 
				// therefore grab only the 1st word for each value
				int j;
				if ((j = value.Find(_T(" "))) != -1)
					value = value.Left(j);
				m_SpecData.m_aJobs.Add( value );
			}
			else
				m_EditedLists= TRUE;
		}
	}
	
	// Add files
	m_SpecData.m_aFile.RemoveAll ( );
	if( m_fileList )
	{
		count=m_fileList->GetCount(); 
				
		for(i=0; i<count; i++)
		{
			m_fileList->GetText(i, value);
			if(value!=_T("") && m_fileList->GetCheck(i)==1)
				m_SpecData.m_aFile.Add( value );
			else
				m_EditedLists= TRUE;
		}
	}

	//		okay, m_SpecData is updated with the new values.
	//		call m_Spec's Format( ), which will call m_SpecData's 
	//		Get( ) function
	//
	try
	{
		StrBuf *str = m_Spec.Format( ( SpecData * )&m_SpecData );
		m_NewForm = CharToCString(str->Text());

		// 98.2 server will bungle a blank password, so just remove that line from the spec
		// in cases where password is blank
		if( ( GET_SERVERLEVEL() < 6 && m_NewPassword.IsEmpty()) )
			RemovePasswordFromSpec();
		
        delete str;

		if (m_FoundLineElemWithValues != -1)	// Do we need to put the "line with values" stuff back?
		{
			if (!RestoreLineElemWithValues( m_NewForm, m_OrigSpecDefStr ))
			{
				AfxMessageBox(IDS_SPEC_COULDNT_RESTORE, MB_ICONSTOP );
				return FALSE;
			}
		}

        CString txt;
        // don't try to display the whole thing, cuz if it's very large FormatMessage
        // will thrown a memory exception.
        txt.FormatMessage(IDS_THE_NEW_FORM_IS_s, m_NewForm.Left(1000));
		AddToStatus( txt, SV_DEBUG );
	}
	catch(...)
	{
		AfxMessageBox( IDS_BAD_SPEC, MB_ICONSTOP );
		return FALSE;
	}
	
	return TRUE;
}

/*
	_________________________________________________________________

	When running against a 98.2 server, the 'Password:' tag must not
	be included if the password is blank.
	_________________________________________________________________
*/

void CP4SpecDlg::RemovePasswordFromSpec()
{
	int start= m_NewForm.Find(_T("\nPassword:"));
	int end= start;
	
	if( end > -1 )
	{
		while( ++end < m_NewForm.GetLength() && m_NewForm[end] != _T('\n') )
			;

		if( end >= m_NewForm.GetLength() )
			m_NewForm= m_NewForm.Left( start );
		else
			m_NewForm= m_NewForm.Left( start ) + m_NewForm.Mid( end );
	}
}



/*
	_________________________________________________________________

	get the text of the control.

	this code assumes that there are only edit boxes and combo boxes
	in the dialogue. 
	_________________________________________________________________
*/

CString CP4SpecDlg::GetNewValue( CWnd *pControl, int i )
{
	CString cst;

	if( pControl->IsKindOf( RUNTIME_CLASS( CP4EditBox ) ) )
	{
		pControl->GetWindowText( cst );
	}
	else if ( pControl->IsKindOf( RUNTIME_CLASS( CComboBox ) ) )
	{
		CComboBox *box = ( CComboBox * )pControl;
		int cursel = box->GetCurSel( );
		if (cursel != CB_ERR)
			 box->GetLBText(cursel, cst );
		else cst.Empty();
	}
	else if ( pControl->IsKindOf( RUNTIME_CLASS( CButton ) )
		   && m_SpecData.GetTypeOf( i ) == SDT_SELECT)
	{
		CButton *pButton = (CButton *)pControl;
		if (pButton->GetCheck())
			pControl->GetWindowText( cst );
		else
			cst = m_specControls[i].tip;
	}
	return cst;
}


/*
	_________________________________________________________________
*/

BOOL CP4SpecDlg::ShowUserError( const CString &msg, CWnd *pControl )
{
	AddToStatus( msg, SV_WARNING);
	AfxMessageBox( msg, MB_ICONEXCLAMATION );
	GotoDlgCtrl( pControl );

	return FALSE;
}


/*
	_________________________________________________________________

	this wrapper is used by p4job, etc., for displaying the data in
	the pane's list view. not all fields are displayed, especially for
	jobs, which are user-defined, so return only those things that
	the list view needs
	_________________________________________________________________
*/

void CP4SpecDlg::GetCP4Wrapper(CObject *wrapper)
{
	CString name;
	CString fullname;
	CString email;
	CString root;
	CString desc;
	CString user;
	CString status;
	CString owner;
	CString host;
	CString options;
		
	//		the date just changed, since its a new spec
	//
	CTime t = CTime::GetCurrentTime();
	CString date;
	date = t.Format( _T("%Y/%m/%d") );

	CString s;

	if( wrapper->IsKindOf( RUNTIME_CLASS( CP4Client ) ) ) 
	{
		for( int i = 0; i< m_SpecData.GetNumItems( ); i++ )
		{
			s = m_SpecData.GetTagOf( i );
			if( s == _T("Client"))
				name = m_SpecData.GetValueOf( i ) ;
			else if ( s == g_tagDescription )
				desc = m_SpecData.GetValueOf( i ) ;
			else if ( s == _T("Root") )
				root = m_SpecData.GetValueOf( i ) ;
			else if ( s == _T("Owner") )
				owner = m_SpecData.GetValueOf( i ) ;
			else if ( s == _T("Host") )
				host = m_SpecData.GetValueOf( i ) ;
			else if ( s == _T("Options") )
				options = m_SpecData.GetValueOf( i ) ;
		}
		( ( CP4Client * )wrapper )->Create( name, owner, host, date, root, desc );
	}
	else if( wrapper->IsKindOf( RUNTIME_CLASS( CP4Job ) ) )
	{
		//		jobs are special, because their fields are
		//		user-defined. So pass all values and codes.
		CStringArray values;
		CDWordArray  codes;
		for( int i = 0; i< m_SpecData.GetNumItems( ); i++ )
		{
			values.Add(m_SpecData.GetValueOf( i ));
			codes.Add(m_SpecData.GetCodeOf( i ));
		}
		( ( CP4Job * )wrapper )->Create( values, codes );

	}

	else if(wrapper->IsKindOf(RUNTIME_CLASS(CP4Branch)))
	{
		for( int i = 0; i< m_SpecData.GetNumItems( ); i++ )
		{
			s = m_SpecData.GetTagOf( i );
			if( s == _T("Branch"))
				name = m_SpecData.GetValueOf( i ) ;
			else if ( s == _T("Owner") )
				owner = m_SpecData.GetValueOf( i ) ;
			else if ( s.GetAt(0) == _T('O') && (s.Find(_T("Option")) == 0) )
			{
				if (options.GetLength() == 0)
					options = m_SpecData.GetValueOf( i ) ;
				else options += CString(_T(" ")) + m_SpecData.GetValueOf( i ) ;
			}
			else if ( s == g_tagDescription )
				desc = m_SpecData.GetValueOf( i ) ;
		}
		( ( CP4Branch * )wrapper )->Create( name, owner, options, date, desc );
	}

	else if(wrapper->IsKindOf(RUNTIME_CLASS(CP4Label)))
	{
		for( int i = 0; i< m_SpecData.GetNumItems( ); i++ )
		{
			s = m_SpecData.GetTagOf( i );
			if(  s == _T("Label") ) 
				name = m_SpecData.GetValueOf( i );
			else if ( s == _T("Owner") )
				owner = m_SpecData.GetValueOf( i ) ;
			else if ( s.GetAt(0) == _T('O') && (s.Find(_T("Option")) == 0) )
			{
				if (options.GetLength() == 0)
					options = m_SpecData.GetValueOf( i ) ;
				else options += CString(_T(" ")) + m_SpecData.GetValueOf( i ) ;
			}
			else if(  s == g_tagDescription ) 
				desc = m_SpecData.GetValueOf( i );
		}
		( ( CP4Label * )wrapper )->Create( name, owner, options, date, desc );
	}

	else if(wrapper->IsKindOf(RUNTIME_CLASS(CP4User)))
	{
		for( int i = 0; i< m_SpecData.GetNumItems( ); i++ )
		{
			s = m_SpecData.GetTagOf( i );
			if(  s == g_tagUser  ) 
				name = m_SpecData.GetValueOf( i );
			else if( s == g_tagFullName ) 
				fullname = m_SpecData.GetValueOf( i );
			else if( s == _T("Email") ) 
				email = m_SpecData.GetValueOf( i );
		}
		( ( CP4User * )wrapper )->Create( name, email, fullname, date );
	}
}

void CP4SpecDlg::OnHelpnotes() 
{
	if(m_InstructionText.GetLength())
    {
		CSpecDescDlg *dlg = new CSpecDescDlg(this->GetParent());
		dlg->SetIsModeless(TRUE);
		dlg->SetWinPosName(_T("SpecInfo"));
		dlg->SetDescription( m_InstructionText, FALSE );
		dlg->SetCaption( LoadStringResource(IDS_P4WIN_SPECIFICATION_NOTES) );
		if (!dlg->Create(IDD_SPECDESC, this->GetParent()))	// display the description dialog box
		{
			dlg->DestroyWindow();	// some error! clean up
			delete dlg;
		}
    }
}

LRESULT CP4SpecDlg::OnP4EndHelpnotes(WPARAM wParam, LPARAM lParam)
{
	CSpecDescDlg *dlg = (CSpecDescDlg *)lParam;
	dlg->DestroyWindow();
	return TRUE;
}

void CP4SpecDlg::OnEditor() 
{
	BOOL bForce = FALSE;
	BOOL bStatus = FALSE;
	BOOL need2check = FALSE;
	CString client = GET_P4REGPTR()->GetP4Client();
	CString port = GET_P4REGPTR()->GetP4Port();
	CString user = GET_P4REGPTR()->GetP4User();
	CString type;
	CString txt;
	CWnd *pWnd= m_specControls[0].control;
	pWnd->GetWindowText(txt);
	switch(m_SpecType)
	{
        case P4BRANCH_SPEC:
            type = _T("branch ");
			if (txt.IsEmpty())
			{
				CNewClientDlg newdlg;
				newdlg.SetNew( NEWBRANCH );
				if( newdlg.DoModal( ) == IDCANCEL )
					return;
				txt = newdlg.GetName( ) ;
				need2check = TRUE;
			}
            break;
		case P4CHANGE_SPEC:
		{
			CString value;
			for( int i = 0; i < m_SpecData.GetNumItems( ); i++ ) 
			{	
				CString tag = m_SpecData.GetTagOf( i );
				if (tag == g_tagStatus)
				{
					value = m_SpecData.GetValueOf ( i );
					break;
				}
			}
			bForce = value == _T("submitted") ? TRUE : FALSE;
			bStatus = GET_SERVERLEVEL() >= 10 ? TRUE : FALSE;

			type = _T("change ");
			if (txt == _T("new"))
				txt.Empty();
			if (GET_SERVERLEVEL() >= 10)
				bStatus = TRUE;
            break;
		}
        case P4CLIENT_SPEC:
            type = _T("client ");
            break;
		case P4JOB_SPEC:
            type = _T("job ");
            break;
        case P4LABEL_SPEC:
            type = _T("label ");
			if (txt.IsEmpty())
			{
				CNewClientDlg newdlg;
				newdlg.SetNew( NEWLABEL );
				if( newdlg.DoModal( ) == IDCANCEL )
					return;
				txt = newdlg.GetName( ) ;
				need2check = TRUE;
			}
            break;
        case P4USER_SPEC:
            type = _T("user ");
            break;
		default:
			ASSERT(0);
    }
	if (need2check)
	{
		BOOL bError = FALSE;
		CCmd_Describe *pCmd = new CCmd_Describe;
		pCmd->Init( NULL, RUN_SYNC, TRUE, m_pCallingCommand->GetServerKey() );
		if( pCmd->Run( m_SpecType, txt ) )
			bError = pCmd->GetError();
		delete pCmd;
		if (bError)
			return;	// the name chosen is already in use for a different type of object.
	}

	CString errorText;
	CString password= GET_P4REGPTR()->GetP4UserPassword();

	BOOL rc;
	int i = 0;
	LPCTSTR argptr[8];

	if (password.GetLength() > 0)
	{
		argptr[i++] = _T("-P");
		argptr[i++] = password;
	}
	argptr[i++] = type;
	if (bForce)
	{
		argptr[i++] = _T("-f");
	}
	if (bStatus)
	{
		argptr[i++] = _T("-s");
	}
	if (StrLen(m_pCallingCommand->GetTemplateName()))
	{
		argptr[i++] = _T("-t");
		argptr[i++] = m_pCallingCommand->GetTemplateName();
	}
	argptr[i++] = txt;
	while (i < 8)
		argptr[i++] = NULL;

	rc = TheApp()->RunApp(P4_APP, RA_WAIT, m_hWnd, FALSE, NULL, errorText, 
				    _T("-p"), port, _T("-c"), client, _T("-u"), user, 
					argptr[0], argptr[1], argptr[2], argptr[3], 
					argptr[4], argptr[5], argptr[6], argptr[7]);
	if (rc)
	{
		if (m_bIsModal)
		{
			CDialog *pParent = (CDialog *)GetParent();
			pParent->EndDialog(IDCANCEL);
		}
		else
			m_pCallingCommand->EndSpecDlg(IDCANCEL);
		::PostMessage(MainFrame()->m_hWnd, WM_COMMAND, ID_VIEW_UPDATE, 0);
	}
	else
	{
		txt.FormatMessage(IDS_SPEC_UNABLE_TO_START_P4_s_s, type, errorText);
		AfxMessageBox(txt, MB_ICONSTOP);
	}
}

BOOL CP4SpecDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if( m_SendingSpec )
		return SET_BUSYCURSOR();
	else
		return CPropertyPage::OnSetCursor(pWnd, nHitTest, message);
}


void CP4SpecDlg::OnSize(UINT nType, int cx, int cy) 
{
	CPropertyPage::OnSize(nType, cx, cy);

	if( m_MinSize.cx > 0 )
	{
		int xDiff= cx - m_MinSize.cx;
		int yDiff= (cy - m_MinSize.cy) / max(1, m_NumMultiLineChildWnds);
		int xOffset;
		int yOffset= 0;

		// Iterate thru child windows:
		// 1) buttons slide right by xDiff
		// 2) statics can slide down but not resize
		// 3) edits and combos stretch

		int stdHeight = m_StdHeight * m_MinLi + EXTRAHEIGHT;
		int i;
		for( i=0; i < m_childControls.GetSize(); i++ )
		{
			int oldHeight;
			int xdif = m_childControls[i].IsHalfWidth() ? xDiff/2 : xDiff;
			xOffset  = m_childControls[i].IsIndent2Middle() ? xDiff/2 
					 : m_childControls[i].IsIndent2Right() ? xDiff : 0;
			CRect windowRect= m_childControls[i].GetOrigRect();

			switch( m_childControls[i].GetType() )
			{
			case CHILD_BUTTON:
				if (i == m_BrowseBtnCtrlID)	// CWnd* of Browse btn must be recalc'ed each time
					m_childControls[i].SetWindow(GetDlgItem(IDC_BROWSE));
				windowRect.OffsetRect(xOffset, yOffset);
				m_childControls[i].GetWindow()->MoveWindow( windowRect );
				break;
			case CHILD_RIGHTSTATIC:
				windowRect.OffsetRect(xDiff, 0);
				m_childControls[i].GetWindow()->MoveWindow( windowRect );
				break;
			case CHILD_MULTILINEEDIT:
			case CHILD_CHECKLISTBOX:
				oldHeight = windowRect.Height();
				windowRect.OffsetRect(xOffset, yOffset);
				windowRect.InflateRect( 0, 0, xdif, yDiff );
				if (m_childControls[i].GetMaxLines())
				{
					windowRect.bottom = windowRect.top 
									  + (m_StdHeight+4) * m_childControls[i].GetMaxLines() 
									  + EXTRAHEIGHT;
					yOffset+= windowRect.Height() - oldHeight;
				}
				else if (windowRect.Height() < stdHeight)
				{
					windowRect.bottom = windowRect.top + stdHeight;
					yOffset+= windowRect.Height() - oldHeight;
				}
				else
					yOffset+= yDiff;
				m_childControls[i].GetWindow()->MoveWindow( windowRect );
				if ( m_childControls[i].GetType() == CHILD_CHECKLISTBOX )
				{
					// Because checklistboxes are an integral height
					// we have to ask the system where the bottom
					// actually is, and adjust accordingly.
					CRect newRect;
					m_childControls[i].GetWindow()->GetWindowRect(newRect);
					ScreenToClient(newRect);
					yOffset+= newRect.Height() - windowRect.Height();
				}
				break;
			case CHILD_STATIC:
			case CHILD_CHECKBOX:
				windowRect.OffsetRect(xOffset, yOffset);
				m_childControls[i].GetWindow()->MoveWindow( windowRect );
				break;
			case CHILD_SINGLELINEEDIT:
			case CHILD_DROPLIST:
				windowRect.OffsetRect(xOffset, yOffset);
				windowRect.InflateRect( 0, 0, xdif, 0 );
				m_childControls[i].GetWindow()->MoveWindow( windowRect );
				break;
			default:
				ASSERT(0);
			}
		}

		CRect rectWindow, rectChild;
		GetWindowRect(&rectWindow);
		if (i)
		{
			m_childControls[i-1].GetWindow()->GetWindowRect(&rectChild);
			int l;
			switch(m_childControls[i-1].GetType())
			{
			case CHILD_MULTILINEEDIT:
				l = m_MinLi;
				break;
			case CHILD_CHECKLISTBOX:
				l = 4;
				break;
			default:
				l = 0;
				break;
			}
			if (l)
			{
				int h = m_StdHeight * l;
				if (rectChild.top < rectWindow.bottom - h - m_StdSpaceV)
				{
					rectChild.bottom = rectWindow.bottom - m_StdSpaceV;
					ScreenToClient(&rectChild);
					m_childControls[i-1].GetWindow()->MoveWindow( rectChild );
				}
			}
		}
		int y_delta = rectWindow.bottom - rectWindow.top - cy;
		SetScrollRange(SB_VERT, 0, 
								rectWindow.Height() <= m_MinSize.cy 
									  ? m_VscrollMax = m_MinSize.cy 
													 + y_delta - cy 
													 + (m_MinSize.cy - (cy - y_delta))/8
									  : 0, TRUE);
	}
	RedrawWindow(NULL,NULL,RDW_INVALIDATE);
}

void CP4SpecDlg::OnSizing(UINT fwSide, LPRECT pRect) 
{
	CPropertyPage::OnSizing(fwSide, pRect);
	
	pRect->right= max( pRect->right, pRect->left + m_MinSize.cx );
	pRect->bottom= max( pRect->bottom, pRect->top + MINHEIGHT );
	pRect->bottom= min( pRect->bottom, m_ScreenHeight );

	ScrollWindow(0, -m_VscrollPos, NULL, NULL);
	SetScrollPos(SB_VERT, m_VscrollPos = 0, TRUE);
}

void CP4SpecDlg::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
	RECT rect;
	SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
	m_ScreenHeight = rect.bottom - rect.top;

	lpMMI->ptMinTrackSize.y = MINHEIGHT;
	lpMMI->ptMaxTrackSize.y = m_ScreenHeight;
	CPropertyPage::OnGetMinMaxInfo(lpMMI);
}

BOOL CP4SpecDlg::OnCommand(WPARAM wParam, LPARAM lParam) 
{
	BOOL rc = CPropertyPage::OnCommand(wParam, lParam);
	if (!m_ChangesHaveBeenMade && m_WindowShown)
	{
		if ((HIWORD(wParam) == EN_UPDATE)
		 || (HIWORD(wParam) == CBN_SELCHANGE))
		{
			CDialog *pParent = (CDialog *)GetParent();
			pParent->GetDlgItem(ID_EDITOR)->EnableWindow( FALSE );
			m_EditorBtnDisabled = m_ChangesHaveBeenMade = TRUE;
		}
	}
	return rc;
}

void CP4SpecDlg::OnShowWindow(BOOL bShow, UINT nStatus) 
{
	CPropertyPage::OnShowWindow(bShow, nStatus);
	
	OnSize(SIZE_RESTORED, m_MinSize.cx, m_MinSize.cy);
	int i;
	if ((i = m_childControls.GetSize()) > 0)
	{
		CRect rectWindow, rectChild;
		GetWindowRect(&rectWindow);
		m_childControls[i-1].GetWindow()->GetWindowRect(&rectChild);
		if (rectChild.bottom + m_StdSpaceV >= rectWindow.bottom)
			m_MinSize.cy = max(MINHEIGHT, rectChild.bottom - rectWindow.top + m_StdSpaceV);
	}

	m_WinPos.SetMinSize( m_MinSize );
	if (!m_AutomaticallyUpdate)
		m_WinPos.RestoreWindowPosition( );

	m_WindowShown = TRUE;
}

void CP4SpecDlg::OnHelp() 
{
	DWORD helpID=0;

	switch(m_SpecType)
	{
        case P4BRANCH_SPEC:
            helpID=TASK_MANAGING_BRANCH_SPECIFICATIONS;
            break;
		case P4CHANGE_SPEC:
			helpID=m_AllowSubmit ? ALIAS_20_SUBMIT : TASK_WORKING_WITH_CHANGELISTS;
            break;
        case P4CLIENT_SPEC:
            helpID=TASK_CREATING_CLIENTS;
            break;
		case P4JOB_SPEC:
            helpID=TASK_MANAGING_JOBS;
            break;
        case P4LABEL_SPEC:
            helpID=TASK_MANAGING_LABELS;
            break;
        case P4USER_SPEC:
            helpID=TASK_MANAGING_USERS;
            break;
		default:
			ASSERT(0);
    }
	AfxGetApp()->WinHelp(helpID);
}

BOOL CP4SpecDlg::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	OnHelp();
	return TRUE;
}

void CP4SpecDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	int nVscrollInc;

	switch (nSBCode)
	{
	case SB_TOP:
		nVscrollInc = -m_VscrollPos;
		break;

	case SB_BOTTOM:
		nVscrollInc = m_VscrollMax - m_VscrollPos;
		break;

	case SB_LINEUP:
		nVscrollInc = -10;
		break;

	case SB_LINEDOWN:
		nVscrollInc = 10;
		break;

	case SB_PAGEUP:
		nVscrollInc = min(-1, -100);
		break;

	case SB_PAGEDOWN:
		nVscrollInc = max(1, 100);
		break;

	case SB_THUMBTRACK:
		nVscrollInc = nPos - m_VscrollPos;
		break;

	default:
		nVscrollInc = 0;
	}

	if ((nVscrollInc = max(-m_VscrollPos, min(nVscrollInc, m_VscrollMax - m_VscrollPos))) != 0)
	{
		m_VscrollPos += nVscrollInc;
		ScrollWindow(0, -nVscrollInc, NULL, NULL);
		SetScrollPos(SB_VERT, m_VscrollPos, TRUE);
	}

	CPropertyPage::OnVScroll(nSBCode, nPos, pScrollBar);
}

void CP4SpecDlg::OnCancelButton()
{
	if (m_SendingSpec 
	 && IDYES == AfxMessageBox(IDS_CANCEL_AREYOUSURE, 
									MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2)
	 && SERVER_BUSY())
		global_cancel = 1;
}

void CP4SpecDlg::OnBrowse()
{
	UpdateData();
	CString txt;
	txt.FormatMessage(IDS_CHOOSE_FOLDER_FOR, m_BrowseTag);
	CString path;
	m_childControls[m_BrowseFldCtrlID].GetWindow()->GetWindowText(path);
	path = TheApp()->BrowseForFolder(m_hWnd, path, txt, BIF_NEWDIALOGSTYLE);
	if (!path.IsEmpty())
	{
		m_childControls[m_BrowseFldCtrlID].GetWindow()->SetWindowText(path);
		UpdateData(FALSE);
	}
}

void CP4SpecDlg::ResetFileChecks(BOOL bCheck)
{
	ASSERT(m_pLastFilesList);
	if (!m_pLastFilesList)
	{
		AfxMessageBox(_T("Change spec appears to be badly damaged!"), MB_OK|MB_ICONERROR);
		return;
	}

	// if they want to move the unchanged files to the default changelist (i.e. "Leave"),
	//	we must Uncheck the unchanged files in the selection set
	// otherwise
	//	we must Check the unchanged files in the selection set
	// Checks for all other files are not touched
	int cnt = m_pLastFilesList->GetCount( );
	for ( int i = -1; ++i < cnt;  )
	{
		CString s;
		m_pLastFilesList->GetText(i, s);

		if (!m_pDeltaView->GetTreeCtrl().IsAMemeberOfSelectionList(s) || IsFileChanged(s))
			continue;

		if (m_UnchangedFlag == 2)	// leave unchanged files?
			m_pLastFilesList->SetCheck( i, 0 );
		else
			m_pLastFilesList->SetCheck( i, 1 );
	}
}