P4win.cpp. #1

  • //
  • guest/
  • YourUncleBob/
  • p4win/
  • main/
  • gui/
  • P4win.cpp.
  • View
  • Commits
  • Open Download .zip Download (74 KB)
//
// Copyright 1997 Nicholas J. Irias.  All rights reserved.
//
//

// P4win.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "P4win.h"
#include "hlp\p4win.hh"

#include "MainFrm.h"
#include "Document.h"
#include "DepotView.h"
#include "resource.h"
#include "TokenString.h"
#include "NewWindowDlg.h"
#include "RegKeyEx.h"
#include "cmd_info.h"
#include "Cmd_Login.h"
#include "ImageList.h"
#include <mapi.h>
#include <winver.h>

#include "GuiClientUser.h"


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

int global_cancel = 0;
CString startingfolder;

/////////////////////////////////////////////////////////////////////////////
// CP4winApp

BEGIN_MESSAGE_MAP(CP4winApp, CP4GuiApp)
	//{{AFX_MSG_MAP(CP4winApp)
	ON_COMMAND(ID_NEW_WINDOW, OnNewWindow)
	ON_UPDATE_COMMAND_UI(ID_NEW_WINDOW, OnUpdateNewWindow)
	//}}AFX_MSG_MAP
	// Standard file based document commands
	ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
	ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
	// Standard print setup command
	ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CP4winApp construction

CP4winApp::CP4winApp()
{
	EnableHtmlHelp();
	m_IdleCounter = 1;
	m_InitialView = _T('\0');
	m_WarningDialog= TRUE;
	m_TestFlag= FALSE;
	m_hNWSRVLOC = LoadLibrary(_T("NWSRVLOC.dll"));
	m_RevHistCount = 0;
	m_RevHistEnableShowIntegs = TRUE;
	m_viewImageList = 0;
	m_toolBarImageList = 0;
	m_RevHistLast = 0;
	m_bFindInChg = FALSE;

	DWORD dummy;
	TCHAR cmdLine[] = _T("P4Merge.exe");
	m_P4Merge = GetFileVersionInfoSize( cmdLine, &dummy ) > 0 ? 1 : 0;
	if (m_P4Merge)
		m_P4MergeVer = MainFrame()->GetExeVersion(cmdLine);
}

CP4winApp::~CP4winApp()
{
	if(m_viewImageList)
	{
		m_viewImageList->Detach();
		delete m_viewImageList;
	}
	if(m_toolBarImageList)
	{
		m_toolBarImageList->Detach();
		delete m_toolBarImageList;
	}
	if(m_hMutex != NULL)
		CloseHandle(m_hMutex);
	if (m_hNWSRVLOC)
		FreeLibrary(m_hNWSRVLOC);
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CP4winApp object

CP4winApp theApp;

/////////////////////////////////////////////////////////////////////////////
// command line parsing

static enum Arg { none, port, user, client, password, view, charset, 
				  selection, fileinfo, revhist, revhistcnt, submit, 
				  diff, find, icons, cr8cli, toolsimp } nextArg = none;

void CP4winApp::ParseArg(LPCTSTR pArg)
{
    // usage: [switches]
    // switches:
    //      -q          : disable warnings
    //      -p port     : set p4port
    //      -u user     : set p4user
    //      -c client   : set p4client
    //      -P password : set p4password
    //      -v view     : set initial view
	//		-C charset	: set initial character set
	//		-s selection: set initial selection
	//		-I fileinfo : set requested file properties path
	//		-H revhist  : set requested revision history path
	//		-m revhistcnt: set requested revision history count
	//		-S submit	: submit changelist containing filepath
	//		-D diff		: diff filepath against depot
	//		-F find		: find filepath in depot pane
	//		-i colors	: set requested icons
	//		-( cr8cli	: run new client wizard (if client doesn't exist)
	//		-T toolsimp	: import custom tools from a file

    if(nextArg == none)
    {
        if(lstrlen(pArg) == 2)
        {
            switch(pArg[1])
            {
			case _T('Q'): m_TestFlag = TRUE;
            case _T('q'): m_WarningDialog = FALSE; return;
            case _T('p'): nextArg = port; return;
            case _T('u'): nextArg = user; return;
            case _T('c'): nextArg = client; return;
            case _T('P'): nextArg = password; return;
            case _T('v'): nextArg = view; return;
			case _T('C'): nextArg = charset; return;
			case _T('s'): nextArg = selection; return;
			case _T('I'): nextArg = fileinfo; return;
			case _T('H'): nextArg = revhist; return;
			case _T('m'): nextArg = revhistcnt; return;
			case _T('S'): nextArg = submit; return;
			case _T('D'): nextArg = diff; return;
			case _T('F'): nextArg = find; return;
			case _T('i'): nextArg = icons; return;
			case _T('('): nextArg = cr8cli; return;
			case _T('T'): nextArg = toolsimp; return;
            }
        }
    }
    else
    {
        Arg thisArg = nextArg;
        nextArg = none;
        switch(thisArg)
        {
        case port:
            if(lstrlen(pArg))
            {
                GET_P4REGPTR()->SetP4Port(pArg, TRUE, FALSE, FALSE);
                return;
            }
        case user:
            if(lstrlen(pArg))
            {
                GET_P4REGPTR()->SetP4User(pArg, TRUE, FALSE, FALSE);
                return;
            }
		case cr8cli:
			m_WarningDialog = FALSE;
			m_RunClientWizOnly = TRUE;;
        case client:
            if(lstrlen(pArg))
            {
                GET_P4REGPTR()->SetP4Client(pArg, TRUE, FALSE, FALSE);
                return;
            }
        case password:
            if(lstrlen(pArg))
            {
                GET_P4REGPTR()->SetP4Password(pArg, TRUE, FALSE, FALSE);
				CCmd_Login *pCmd = new CCmd_Login;				// run p4 login
				CString portStr = GET_P4REGPTR()->GetP4Port();	// get current port
				pCmd->GetClient()->SetPort(portStr);			// run login against cur port
				CString userStr = GET_P4REGPTR()->GetP4User();	// get current user
				pCmd->GetClient()->SetUser(userStr);			// run login against cur user
				pCmd->Init(NULL, RUN_SYNC, LOSE_LOCK, 0);
				pCmd->Run(pArg);								// run the login command
				delete pCmd;
                return;
            }
        case view:
            if(lstrlen(pArg))
            {
                m_InitialView = (TCHAR)CharUpper((LPTSTR)(TBYTE)pArg[0]);
                return;
            }
        case charset:
            if(lstrlen(pArg))
            {
                GET_P4REGPTR()->SetP4Charset(pArg, TRUE, FALSE, FALSE);
				GET_P4REGPTR()->SetP4CharsetFromCmdli(TRUE);	// must follow SetP4Charset()!
                return;
            }
        case selection:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
                m_ExpandPath = DemanglePath(pArg);
				if (m_ExpandPath.GetAt(0) != _T('/')
				 && m_ExpandPath.GetAt(1) != _T(':')
				 && m_ExpandPath.GetAt(0) != _T('\\'))
				{
					TCHAR buf[MAX_PATH+1];
					if (GetCurrentDirectory(sizeof(buf)-1, buf))
					{
						CString str = CString(buf) + _T('\\') + m_ExpandPath;
						m_ExpandPath = str;
					}
				}
				m_bFindInChg = TRUE;
                return;
            }
		case fileinfo:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
                m_FileInfoPath = DemanglePath(pArg);
                return;
            }
		case revhist:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
                m_RevHistPath = DemanglePath(pArg);
                return;
            }
		case revhistcnt:
            if(lstrlen(pArg))
            {
				m_RevHistCount = _tstoi(pArg);
                return;
            }
		case submit:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
				if (m_SubmitPath.IsEmpty())
					m_SubmitPath = DemanglePath(pArg);
				else
					m_SubmitPathList.AddTail(DemanglePath(pArg));
				nextArg = submit;
                return;
            }
		case diff:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
                m_DiffPath = DemanglePath(pArg);
                return;
            }
		case toolsimp:
            if(lstrlen(pArg))
            {
				m_WarningDialog = FALSE;
                m_ToolsImportPath = DemanglePath(pArg);
                return;
            }
		case icons:
			GET_P4REGPTR()->SetUse256colorIcons((_tstoi(pArg) == 256) ? TRUE : FALSE);
			return;
        }
    }
	if (*(pArg+1) == _T(':'))
	{
		nextArg = revhist;
		ParseArg(pArg);
		return;
	}
    ASSERT(0);
    m_bGoodArgs = false;
}

/////////////////////////////////////////////////////////////////////////////
// CP4winApp initialization

BOOL CP4winApp::InitInstance()
{
	if(!CP4GuiApp::InitInstance())
        return FALSE;

	LoadStdProfileSettings(0);  // Do NOT track MRU files

	// Get Perforce connect info from registry
	m_RegInfo.ReadRegistry();

    m_bGoodArgs = true;
    ParseCommandLineArgs();
    if(!m_bGoodArgs)
    {
        MessageBox(NULL, LoadStringResource(IDS_INVALID_COMMAND_LINE_ARGS__USAGE), 
			             LoadStringResource(IDS_P4WINUSAGE), MB_ICONEXCLAMATION);
        return FALSE;
    }

	///////////////////
	// Test for previous instance, by attempting to open a named mutex
	// If an instance is already running, see if user wants to activate it

	m_WM_ACTIVATE= RegisterWindowMessage(_T("Activate Previous P4Win"));
	m_WM_SENDCMD = RegisterWindowMessage(_T("P4Win Send Command"));
	m_WM_RPLYCMD = RegisterWindowMessage(_T("P4Win Reply Command"));
	m_hMutex= OpenMutex(SYNCHRONIZE, FALSE, _T("Running P4Win Instance"));
	if(m_hMutex==NULL)
	{
		// This is the first instance
		m_hMutex= CreateMutex(NULL, FALSE, _T("Running P4Win Instance"));
	}
	else if (!m_ExpandPath.IsEmpty())
	{
		BOOL b = FALSE;
		HANDLE hMapFile = CreateFileMapping(
							INVALID_HANDLE_VALUE,		// use paging file
							NULL,						// default security 
							PAGE_READWRITE,				// read/write access
							0,							// max. object size 
							P4WIN_SHARED_MEMORY_SIZE,	// buffer size  
							P4WIN_SHARED_MEMORY_NAME);	// name of mapping object
		 
		if (hMapFile != NULL && hMapFile != INVALID_HANDLE_VALUE) 
		{ 
			EXPANDPATH *ep = (EXPANDPATH *)MapViewOfFile(hMapFile,   // handle to mapping object
											FILE_MAP_ALL_ACCESS, 0, 0, P4WIN_SHARED_MEMORY_SIZE);           
			if (ep != NULL) 
			{ 
				TCHAR *p = ep->buf;
				lstrcpy(p, GET_P4REGPTR()->GetP4Port());
				ep->port = p - ep->buf;
				p += lstrlen(p) + 1;
				lstrcpy(p, GET_P4REGPTR()->GetP4Client());
				ep->client = p - ep->buf;
				p += lstrlen(p) + 1;
				lstrcpy(p, GET_P4REGPTR()->GetP4User());
				ep->user = p - ep->buf;
				p += lstrlen(p) + 1;
				lstrcpy(p, m_ExpandPath);
				ep->path = p - ep->buf;
				::SendMessage( HWND_BROADCAST, m_WM_SENDCMD, (WPARAM)1, (LPARAM)0 );
				Sleep(500);
				if (ep->flag)
					b = TRUE;
				UnmapViewOfFile(ep);
			}
			CloseHandle(hMapFile);
			if (b)
				return FALSE;
		}
	}
	else if( m_WarningDialog )
	{
		//		See if user wants to activate previous instance
		//
		int ret = AfxMessageBox(IDS_P4WIN_IS_ALREADY_RUNNING__OPEN_THE_OLD_WINDOW,
				        MB_YESNOCANCEL  | MB_ICONEXCLAMATION ) ;

		//		only when user picks NO do we run the app
		//
		switch( ret )
		{
		case IDYES: 
			::SendMessage( HWND_BROADCAST, m_WM_ACTIVATE, 0, 0 );
		case IDCANCEL: 
			return FALSE;
		}
	}

	// Load up the shared image lists
	m_viewImageList = new CP4ViewImageList();
	m_toolBarImageList = new CP4WinToolBarImageList();
	if(GET_P4REGPTR()->Use256colorIcons())
	{
		m_viewImageList->Use256ColorIcons();
		m_toolBarImageList->Use256ColorIcons();
	}
	m_viewImageList->Create();
	m_toolBarImageList->Create();
	
	// Load up the busy cursor, after reading registry cwinapp
	m_hBusyCursor= GET_P4REGPTR()->GetP4BusyCursor() ? ::LoadCursorFromFile(_T("P4_Busy.ani")) : NULL;
	if(m_hBusyCursor==NULL)
		m_hBusyCursor= LoadStandardCursor(IDC_WAIT);

	// Write our version number to the registry so P4wei knows what version we are.
	LPCTSTR sKey = _T("Software\\Perforce\\P4Win\\");
	LPCTSTR sVersion = _T("Version");
    CRegKeyEx key;
    if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, sKey, REG_NONE, REG_OPTION_NON_VOLATILE, KEY_WRITE))
        key.SetValueString(m_appVersion, sVersion);

	// Register the application's document templates.  Document templates
	//  serve as the connection between documents, frame windows and views.

	CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CP4winDoc),
		RUNTIME_CLASS(CMainFrame),       // main SDI frame window
		RUNTIME_CLASS(CDepotView));
	AddDocTemplate(pDocTemplate);

	int i;
	CString ver = ((CP4GuiApp*)AfxGetApp())->GetAppVersionString();
	CString info = ver.Mid(3,3) + _T(" ");
	if ((i = ver.ReverseFind(_T('.'))) != -1)
		ver.Delete(i);
#ifdef UNICODE
    info += _T("U");
#endif
	CString info_ver = info + ver;
	info_ver.Replace(' ', '.');
	CharString cs = CharFromCString(info_ver);
	strcpy(m_version, cs);

	OnFileNew();

	// Make sure our name is P4Win, not P4win or p4win
	if (m_pszAppName && *m_pszAppName && *(m_pszAppName+1) == '4')
	{
		TCHAR *p = (TCHAR *)m_pszAppName;
		*p = _totupper(*m_pszAppName);
		*(p+2) = _totupper(*(m_pszAppName+2));
	}

	return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Set a new busy state for Perforce server

BOOL CP4winApp::GetServerLock(int &key) 
{ 
	// Actually set busy
	BOOL gotLock= m_CS.GetServerLock( key); 

	// Get and set cursor position - Windows will then generate the necessary
	// WM_SETCURSOR messages to get the right OnSetCursor() function called

    if( gotLock )
    {
	    POINT pt;
	    if(::GetCursorPos(&pt))
		    ::SetCursorPos(pt.x, pt.y);
    }
    return gotLock;
}

void CP4winApp::ReleaseServerLock(int &key) 
{ 
	m_CS.ReleaseServerLock( key ); 

	// Get and set cursor position - Windows will then generate the necessary
	// WM_SETCURSOR messages to get the right OnSetCursor() function called

    POINT pt;
	if(::GetCursorPos(&pt))
	    ::SetCursorPos(pt.x, pt.y);
}


////////////////////////////////////////////////////////////////////////////
// Access functions for shared image list.  

int CP4winApp::GetFileImageIndex(CP4FileStats *fs, BOOL IsChangesWindow)
{
	// Start at first file
	int state;

	// What type of file is it?
	CString fileType = fs->GetHeadType();
	int iType = ((fileType.Find(_T("text")) != -1) || (fileType.Find(_T("symlink")) != -1)) ? 0 : 1;

	// get base image:
	if(!IsChangesWindow && fs->GetHaveRev() > fs->GetHeadRev() &&
			fs->GetMyOpenAction() == 0 )
	{
		// A ghost file.  We Have the file, but its outside the
		// client view.
		state = CP4ViewImageList::FSB_GHOST;
	}
	else if(iType)
	{
		// normal binary file
		state = CP4ViewImageList::FSB_BINARY;
	}
	else
	{
		// normal text file
		state = CP4ViewImageList::FSB_TEXT;
	}

	// lock state overlay
	if(fs->IsMyLock() || fs->IsMyOpenExclusive())
		state |= CP4ViewImageList::FSB_YOUR_LOCK;
	else if(fs->IsOtherLock() || fs->IsOtherOpenExclusive())
		state |= CP4ViewImageList::FSB_THEIR_LOCK;

	// my action overlay
	switch(fs->GetMyOpenAction())
	{
	case F_ADD:
	case F_BRANCH:
	case F_IMPORT:
		state |= CP4ViewImageList::FSB_YOUR_ADD;
		break;
	case F_EDIT:
	case F_INTEGRATE:
		state |= CP4ViewImageList::FSB_YOUR_EDIT;
		break;
	case F_DELETE:
		state |= CP4ViewImageList::FSB_YOUR_DELETE;
		break;
	}

	// other action overlay
	switch(fs->GetOtherOpenAction())
	{
	case F_ADD:
	case F_BRANCH:
	case F_IMPORT:
		state |= CP4ViewImageList::FSB_THEIR_ADD;
		break;
	case F_EDIT:
	case F_INTEGRATE:
		state |= CP4ViewImageList::FSB_THEIR_EDIT;
		break;
	case F_DELETE:
		state |= CP4ViewImageList::FSB_THEIR_DELETE;
		break;
	}

	// sync state overlay, depends on which view we're in
	if(IsChangesWindow)
	{
		if(fs->IsUnresolved())
			state |= CP4ViewImageList::FSB_NOT_SYNCED;
	}
	else
	{
		if(fs->GetHaveRev())
		{
			if(fs->GetHaveRev() >= fs->GetHeadRev() || 
				fs->GetMyOpenAction() == F_ADD)
				state |= CP4ViewImageList::FSB_SYNCED;
			else if(fs->GetHaveRev() < fs->GetHeadRev())
				state |= CP4ViewImageList::FSB_NOT_SYNCED;
		}
	}
	return CP4ViewImageList::GetFileIndex(state);
}

/*
	_________________________________________________________________
*/

void CP4winApp::StatusAdd( LPCTSTR txt, StatusView level, bool showDialog )
{
	//		get out if we call with garbage or an empty string
	//
	if ( txt == NULL )
		return;
	if ( lstrlen( txt ) < 1 )
		return;

	LPTSTR msg = new TCHAR[ lstrlen( txt ) + 1 ];
	lstrcpy( msg, txt );

	//		don't post the message until we get a window. since i 
	//		put up messages constantly, i can easily post before a 
	//		window is up. this is cheesy, but simpler than doing an
	//		onidle(), and after all, these are just boring status messages.
	//
	CWnd *pWnd = AfxGetApp( )->m_pMainWnd;
	if ( pWnd )
		::PostMessage( pWnd->m_hWnd, WM_STATUSADD, ( WPARAM ) msg, MAKELPARAM(level, showDialog ? 1 : 0));	
	else
		delete [ ] msg;
}

void CP4winApp::StatusAdd( CStringArray *pArray, StatusView level, bool showDialog )
{
	//		get out if we call with garbage or an empty string
	//
	if ( pArray == NULL )
		return;
	
	//		don't post the message until we get a window. since i 
	//		put up messages constantly, i can easily post before a 
	//		window is up. this is cheesy, but simpler than doing an
	//		onidle(), and after all, these are just boring status messages.
	//

	CWnd *pWnd = AfxGetApp( )->m_pMainWnd;
	if ( pWnd )
		::PostMessage( pWnd->m_hWnd, WM_STATUSADDARRAY, ( WPARAM ) pArray, MAKELPARAM(level, showDialog ? 1 : 0));	
	else
		delete [ ] pArray;
}


void CP4winApp::WinHelp(DWORD dwData, UINT nCmd) 
{
	// MFC automatically assigns help IDs for all menu commands,
	// dialogs and frames (see p4win.hm as produced by makehelp.bat).  
	// For the time being, only helpIDs that come from the p4win.hh 
	// file (sequential IDs that start at 1) will be taken as context 
	// help.  Every other help request will be sent to the help contents.
	//
	// TODO:  The menu commands (0x10001 to 0x1ffff) could all have popup
	//        context menues.  But if a single menu command help id is
	//        missing in the help file, there will be an ugly message
	//        for the user.
	//
	//        An alternative is to direct all menu command IDs to a single
	//        topic in the help file
	//
	//		  Yet another option is to use the IDs in p4win.hm for everything
	//        and then set up enough aliases so that nothing falls thru.  The
	//        robohelp help compiler warns about .hm entries that dont have
	//        topics.

	if(dwData < 0x10000 && nCmd != HH_HELP_FINDER)
		CWinApp::HtmlHelp(dwData, HH_HELP_CONTEXT);
	else
		CWinApp::HtmlHelp(dwData, HH_HELP_FINDER);
}


// Spawn a helper app.  For console applications, a batch file is used to ensure
// that filename arguments with ebmedded spaces are passed correctly.  

BOOL CP4winApp::RunApp(int app, RunAppMode mode, HWND hWnd, BOOL isUnicode,
					   RUNAPPTHREADINFO *lprati, CString &errorText, 
					   LPCTSTR arg1, LPCTSTR arg2, LPCTSTR arg3, LPCTSTR arg4, 
					   LPCTSTR arg5, LPCTSTR arg6, LPCTSTR arg7, LPCTSTR arg8, 
					   LPCTSTR arg9, LPCTSTR arg10,LPCTSTR arg11,LPCTSTR arg12,
					   LPCTSTR arg13,LPCTSTR arg14,LPCTSTR arg15,LPCTSTR arg16,
					   LPCTSTR arg17)
{
	CString orgArgX;
	CString orgArgY;
	TCHAR	buf1[LONGPATH*2 + 1];
	TCHAR	buf2[LONGPATH + 1];
	TCHAR	buf3[LONGPATH + 1];
	TCHAR	buf4[LONGPATH + 1];
	TCHAR	buf5[LONGPATH + 1];
	int		i;

	// Find out if we're running NT or win95
	OSVERSIONINFO osVer;
	osVer.dwOSVersionInfoSize= sizeof(OSVERSIONINFO);
	GetVersionEx(&osVer);
	
	// Set up the spawn operation
#ifdef UNICODE
	DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT; 
#else
	DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS;
#endif
	PROCESS_INFORMATION procInfo;
	STARTUPINFO startInfo;
	TCHAR cmdTitle[255];
	GetStartupInfo(&startInfo);
	startInfo.lpReserved= startInfo.lpDesktop= NULL; 
	startInfo.dwFlags |= STARTF_USESHOWWINDOW;
	startInfo.wShowWindow = SW_SHOWNORMAL;

	// Find out what app we're running
	CString appName, appText, title;
	int numArgs = 0;
	int bInternal = 0;
	BOOL isConsole=FALSE;
	BOOL isClose=FALSE;
	BOOL bSquid=FALSE;

	switch(app)
	{
	case DIFF_APP:
	{
		orgArgX = arg4;
		orgArgY = arg6;

		// First, get the file extension, if any, and find out if
		// it has an associated diff for that extension
		appName.Empty();
		CString extension = GetFilesExtension(arg2);
		if (!extension.CompareNoCase(_T("tmp")))
			extension = GetFilesExtension(arg1);
		if(!extension.IsEmpty())
			appName= GET_P4REGPTR()->GetAssociatedDiff(extension);

		if (!appName.IsEmpty())
		{
			bInternal = 0;
			CString appProg = appName;
			if ((i = appProg.ReverseFind(_T('\\'))) > -1)
				appProg = appProg.Right(appProg.GetLength() - i - 1);
			if (appProg == _T("P4Diff.exe"))
				bInternal = 2;
			else if (!appProg.CompareNoCase(_T("Squid.exe")))
				bSquid = TRUE;
		}
		else if ((bInternal = GET_P4REGPTR()->GetDiffInternal()) >= 2)
		{
			bInternal = 2;
			appName= _T("P4Diff.exe");
		}
		else if (bInternal == 1)
		{
			if (m_P4Merge && (osVer.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS))
			{
				appName= _T("P4Merge.exe");
			}
			else
			{
				bInternal = 2;
				appName= _T("P4Diff.exe");
			}
		}
		else
		{
			appName= GET_P4REGPTR()->GetDiffApp();
			CString appProg = appName;
			if ((i = appProg.ReverseFind(_T('\\'))) > -1)
				appProg = appProg.Right(appProg.GetLength() - i - 1);
			if (appProg == _T("P4Diff.exe"))
				bInternal = 2;
			else if (!appProg.CompareNoCase(_T("Squid.exe")))
				bSquid = TRUE;
			else
			{
				// non-Perforce diff app may require DOS console to work correctly
				isConsole= GET_P4REGPTR()->GetDiffAppIsConsole();
				isClose  = GET_P4REGPTR()->GetDiffAppIsClose();
			}
		}
		appText= LoadStringResource(IDS_APP_DIFF);
		title= LoadStringResource(IDS_TITLE_DIFF);
		numArgs=2;
		if (arg1!=NULL && lstrlen(arg1) > 0)
		{
			if (*(arg1+1) == _T(':'))
			{
				if (GetFileAttributes(arg1) == -1)
				{
					appText.FormatMessage(IDS_DIFF_FILENOTFOUND, arg1);
					AddToStatus(appText, SV_ERROR);
					return FALSE;
				}
			}
		}
		else
		{
			ASSERT(0);
		}
		if (arg2!=NULL && lstrlen(arg2) > 0)
		{
			if (*(arg2+1) == _T(':'))
			{
				if (GetFileAttributes(arg2) == -1)
				{
					appText.FormatMessage(IDS_DIFF_FILENOTFOUND, arg2);
					AddToStatus(appText, SV_ERROR);
					return FALSE;
				}
			}
		}
		else
		{
			ASSERT(0);
		}
		if (bInternal == 2)
		{
			ASSERT(arg7==NULL && arg8==NULL && arg9==NULL);
			switch (GET_P4REGPTR()->GetWhtSpFlag())
			{
			case 1:
				arg7 = _T("-b");
				break;
			case 2:
				arg7 = _T("-w");
				break;
			default:
				arg7 = _T("-e");
				break;
			}
			_stprintf(buf1, _T("%d"), -1);	// Temp until implment passing context
			_stprintf(buf2, _T("%d"), GET_P4REGPTR()->GetTabWidth());
			arg8 = buf1;
			arg9 = buf2;
			// On win/9x, we can't have a nice long command line - throw away -r and -l
			if (!arg3 || (osVer.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS))
			{
				arg3 = arg7;
				arg4 = arg8;
				arg5 = arg9;
				arg6 = NULL;
				numArgs=5;
			}
			else if (!arg5)
			{
				ASSERT(arg3!=NULL && lstrlen(arg3) > 0);
				ASSERT(arg4!=NULL && lstrlen(arg4) > 0);
				arg5 = arg7;
				arg6 = arg8;
				arg7 = arg9;
				arg8 = NULL;
				numArgs=7;
			}
			else
				numArgs=9;
		}
		else if (bInternal)
		{
			ASSERT(arg7==NULL && arg8==NULL && arg9==NULL);
			LPCTSTR sav1 = arg1;
			LPCTSTR sav2 = arg2;
			LPCTSTR sav4 = arg4;
			LPCTSTR sav6 = arg6;
			switch (GET_P4REGPTR()->GetWhtSpFlag())
			{
			case 0:
				arg1 = _T("-dl");
				break;
			case 1:
				arg1 = _T("-db");
				break;
			case 2:
				arg1 = _T("-dw");
				break;
			case 3:
				arg1 = _T("-da");
				break;
			default:
				arg1 = _T(" ");
				break;
			}
			_stprintf(buf1, _T("%d"), GET_P4REGPTR()->GetTabWidth());
			arg2 = _T("-tw");
			arg3 = buf1;
			arg4 = _T("-nl");
			arg5 = sav4 ? sav4 : sav1;
			arg6 = _T("-nr");
			arg7 = sav6 ? sav6 : sav2;
			arg8 = sav1;
			arg9 = sav2;
			numArgs=9;
		}
		else if (bSquid)
		{
			// On win/9x, we can't have a nice long command line - throw away -r and -l
			if (!arg3 || (osVer.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS))
			{
				arg3 = arg4 = arg5 = arg6 = NULL;
				numArgs=2;
			}
			else
				numArgs = arg5 ? 6 : 4;
		}
		break;
	}
	case EDIT_APP:
	    appName= GET_P4REGPTR()->GetEditApp();
		appText= LoadStringResource(IDS_APP_EDITOR);
		title.FormatMessage(IDS_TITLE_EDITOR_s, appName);
		numArgs=1;
		isConsole= GET_P4REGPTR()->GetEditAppIsConsole();
//		isClose  = GET_P4REGPTR()->GetEditAppIsClose();
		ASSERT(arg1!=NULL && lstrlen(arg1) > 0);
		ASSERT(arg2==NULL && arg3==NULL && arg4==NULL);
		break;
	case TREE_APP:
#ifdef UNICODE
		switch(MainFrame()->HaveP4QTree())
		{
			default:
			case 1:
				if (MainFrame()->GetP4Vver() < 20051)
				{
					DWORD ver = MainFrame()->GetP4Vver();
					DWORD yr = ver / 10;
					DWORD pt = ver - (yr*10);
					CString txt;
					txt.FormatMessage(IDS_P4VTOOOLD, yr, pt, _T("2005.1"));
					AfxMessageBox(txt, MB_ICONEXCLAMATION);
					return FALSE;
				}
				appName= _T("p4v.exe");
				startInfo.wShowWindow = SW_SHOWNORMAL;
				ASSERT(arg10 !=NULL && lstrlen(arg10) > 0);
				ASSERT(arg15==NULL);
				numArgs = (arg11 == NULL) ? 10 : (arg13 == NULL) ? 12 : 14;
				switch(numArgs)
				{
				case 10:
					if (WideCharToMultiByte(CP_UTF8, 0, arg10, -1, 
											(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
						arg10 = buf1;
					break;
				case 12:
					if (WideCharToMultiByte(CP_UTF8, 0, arg12, -1, 
											(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
						arg12 = buf1;
					break;
				case 14:
					if (WideCharToMultiByte(CP_UTF8, 0, arg14, -1, 
											(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
						arg14 = buf1;
					break;
				default:
					ASSERT(0);
					break;
				}
				break;
			case 2:
				appName= _T("p4tree.exe");
				startInfo.wShowWindow = SW_SHOWMAXIMIZED;
				ASSERT(arg9 !=NULL && lstrlen(arg9) > 0);
				ASSERT(arg14==NULL);
				numArgs = (arg10 == NULL) ? 9 : (arg12 == NULL) ? 11 : 13;
				break;
			case 3:
				appName= _T("P4QTree.exe");
				startInfo.wShowWindow = SW_SHOWMAXIMIZED;
				ASSERT(arg9 !=NULL && lstrlen(arg9) > 0);
				ASSERT(arg14==NULL);
				numArgs = (arg10 == NULL) ? 9 : (arg12 == NULL) ? 11 : 13;
				break;
		}
		appText= LoadStringResource(IDS_APP_TREE);
		title.FormatMessage(IDS_TITLE_EDITOR_s, appName);	// Can use the same string as the editor
		break;
#else
		return FALSE;
#endif
	case ANNOTATE_APP:
#ifdef UNICODE
		if (MainFrame()->HaveTLV())
		{
			if (MainFrame()->GetP4Vver() < 20051)
			{
				DWORD ver = MainFrame()->GetP4Vver();
				DWORD yr = ver / 10;
				DWORD pt = ver - (yr*10);
				CString txt;
				txt.FormatMessage(IDS_P4VTOOOLD, yr, pt, _T("2005.1"));
		        AfxMessageBox(txt, MB_ICONEXCLAMATION);
				return FALSE;
			}
			appName= _T("p4v.exe");
			startInfo.wShowWindow = SW_SHOWNORMAL;
			ASSERT(arg10 !=NULL && lstrlen(arg10) > 0);
			ASSERT(arg15==NULL);
			numArgs = (arg11 == NULL) ? 10 : (arg13 == NULL) ? 12 : 14;
			switch(numArgs)
			{
			case 10:
				if (WideCharToMultiByte(CP_UTF8, 0, arg10, -1, 
										(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
					arg10 = buf1;
				break;
			case 12:
				if (WideCharToMultiByte(CP_UTF8, 0, arg12, -1, 
										(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
					arg12 = buf1;
				break;
			case 14:
				if (WideCharToMultiByte(CP_UTF8, 0, arg14, -1, 
										(LPSTR)buf2, sizeof(buf2), NULL, NULL)
					 && MultiByteToWideChar(1252, 0, (LPSTR)buf2, -1, buf1, sizeof(buf1)))
					arg14 = buf1;
				break;
			default:
				ASSERT(0);
				break;
			}
		}
		startInfo.wShowWindow = SW_SHOWNORMAL;
		appText= LoadStringResource(IDS_APP_TREE);
		title.FormatMessage(IDS_TITLE_EDITOR_s, appName);	// Can use the same string as the editor
		break;
#else
		return FALSE;
#endif
	case MERGE_APP:
	{
		orgArgX = arg3;
		orgArgY = arg5;

		ASSERT(arg1!=NULL && lstrlen(arg1) > 0);
		ASSERT(arg2!=NULL && lstrlen(arg2) > 0);
		ASSERT(arg3!=NULL && lstrlen(arg3) > 0);
		ASSERT(arg4!=NULL && lstrlen(arg4) > 0);

		// First, get the file extension, if any, and find out if
		// it has an associated Merge for that extension
		appName.Empty();
		CString extension = GetFilesExtension(arg3);
		if (!extension.CompareNoCase(_T("tmp")))
			extension = GetFilesExtension(arg2);
		if (!extension.CompareNoCase(_T("tmp")))
			extension = GetFilesExtension(arg1);
		if(!extension.IsEmpty())
			appName= GET_P4REGPTR()->GetAssociatedMerge(extension);

		if (!appName.IsEmpty())
		{
			numArgs=4;
			bInternal=0;
		}
		else
		{
			bInternal = GET_P4REGPTR()->GetMergeInternal();
			switch (bInternal)
			{
			case 0:
			{
				appName= GET_P4REGPTR()->GetMergeApp();
				CString appProg = appName;
				if ((i = appProg.ReverseFind(_T('\\'))) > -1)
					appProg = appProg.Right(appProg.GetLength() - i - 1);
				if ((appProg == _T("P4WinMrg.exe")) && (appName.Find(_T("raxis")) == -1))
					bInternal = 1;
				else
				{
					isConsole= GET_P4REGPTR()->GetMergeAppIsConsole();
					isClose  = GET_P4REGPTR()->GetMergeAppIsClose();
					numArgs=4;
				}
				break;
			}
			default:
			case 2:
			{
				if (m_P4Merge && (osVer.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS))
				{
					appName= _T("P4Merge.exe");
					break;
				}
				bInternal = 1;
			}
			case 1:
				appName= _T("P4WinMrg.exe");
				break;
			}
		}
		if (bInternal == 1)
		{
			BOOL bNSF = GET_P4REGPTR()->GetMergeNSF();
			isConsole = isClose = FALSE;
			LPCTSTR svArg = arg5;
			arg9 = arg4;	// result
			arg8 = arg3;	// yours
			arg7 = arg2;	// theirs
			arg6 = arg1;	// base
			switch (GET_P4REGPTR()->GetMrgWhtSpFlag())
			{
			case 1:
				arg5 = _T("-b");
				break;
			case 2:
				arg5 = _T("-w");
				break;
			default:
				arg5 = _T("-s");
				break;
			}
			lstrcpy(buf4, m_bNoCRLF ? _T("-lf") : _T("-crlf"));
			_stprintf(buf3, _T("\"-i%d\""), GET_P4REGPTR()->GetMrgTabWidth());
            if(bNSF)
                lstrcpy(buf2, _T("-nsf"));
            else
            {
                CString msg;
                msg.FormatMessage(IDS_WILL_REPLACE_s_IF_MERGE_ACCEPTED, arg8);	// yours
                lstrcpy(buf2, msg);
            }
			_stprintf(buf1, _T("\"-t%s\""), svArg);
			arg4 = buf4;
			arg3 = buf3;
			arg2 = buf2;
			arg1 = buf1;
			numArgs=9;
		}
		else if (bInternal)
		{
			isConsole = isClose = FALSE;
			LPCTSTR svArg = arg5;
			arg17 = arg4;	// result
			arg16 = arg3;	// yours
			arg15 = arg2;	// theirs
			arg14 = arg1;	// base
			switch (GET_P4REGPTR()->GetMrgWhtSpFlag())
			{
			case 0:
				arg13 = _T("-dl");
				break;
			case 1:
				arg13 = _T("-db");
				break;
			case 2:
				arg13 = _T("-dw");
				break;
			case 3:
				arg13 = _T("-da");
				break;
			default:
				arg13 = _T(" ");
				break;
			}
			_stprintf(buf5, _T("\"%s\""), *arg6 ? arg6 : arg14);
			_stprintf(buf4, _T("%d"), GET_P4REGPTR()->GetMrgTabWidth());
            CString msg;
            msg.FormatMessage(IDS_WILL_REPLACE_s_IF_MERGE_ACCEPTED, arg16);	// yours
			if (msg.GetAt(0) == _T('-') && msg.GetAt(1) == _T('m'))
				msg = msg.Mid(2);
            lstrcpy(buf3, msg);
			_stprintf(buf2, _T("\"%s\""), arg16);
			_stprintf(buf1, _T("\"%s\""), svArg);
			arg12 = m_bNoCRLF ? _T("unix") : _T("win");
			arg11 = _T("-le");
			arg10 = buf4;
			arg9 = _T("-tw");
			arg8 = buf3;
			arg7 = _T("-nm");
			arg6 = buf2;
			arg5 = _T("-nr");
			arg4 = buf1;
			arg3 = _T("-nl");
			arg2 = buf5;
			arg1 = _T("-nb");
			numArgs=17;
		}
		appText= LoadStringResource(IDS_APP_MERGE);
		title.FormatMessage(IDS_TITLE_MERGE, appName);
		break;
	}
	case P4_APP:
	    appName= _T("p4");
		appText= LoadStringResource(IDS_APP_P4);
		title.FormatMessage(IDS_TITLE_P4, appName);
		ASSERT(arg1!=NULL && lstrlen(arg1) > 0);
		ASSERT(arg2!=NULL && lstrlen(arg2) > 0);
		ASSERT(arg3!=NULL && lstrlen(arg3) > 0);
		ASSERT(arg4!=NULL && lstrlen(arg4) > 0);
		ASSERT(arg5!=NULL && lstrlen(arg5) > 0);
		ASSERT(arg6!=NULL && lstrlen(arg6) > 0);
		ASSERT(arg7!=NULL && lstrlen(arg7) > 0);
		for (numArgs = 7; ++numArgs < 14; )
		{
			if (numArgs == 8)
			{
				if (!arg9)
					break;
			}
			else if (numArgs == 9)
			{
				if (!arg10)
					break;
			}
			else if (numArgs == 10)
			{
				if (!arg11)
					break;
			}
			else if (numArgs == 11)
			{
				if (!arg12)
					break;
			}
			else if (numArgs == 12)
			{
				if (!arg13)
					break;
			}
			else if (numArgs == 13)
			{
				if (!arg14)
					break;
			}
		}
		startInfo.dwFlags = STARTF_USESHOWWINDOW;
		startInfo.wShowWindow = SW_SHOWNORMAL;
		dwCreationFlags |= CREATE_NEW_CONSOLE;
		break;
	default:
		ASSERT(0);
		return FALSE;
	}
	if(isConsole)
	{
		lstrcpy(cmdTitle, title);
		startInfo.lpTitle= cmdTitle;
		startInfo.dwXCountChars=80; 
		startInfo.dwYCountChars=1000; 
		startInfo.dwFlags=STARTF_USECOUNTCHARS; 
		startInfo.cbReserved2=0; 
		startInfo.lpReserved2=NULL; 
	}

	// If we drew a blank for the appname, return the error
	if(appName.GetLength()==0)
	{
		errorText.FormatMessage(IDS_MISSING_APPNAME_s_s, appText, appText);
		return FALSE;
	}

	// Copy the arguments, and make sure args with embedded spaces have quotes
	// this is primitive, but it saves code in calling functions
	CString args[17];
	args[0]=arg1;
	if(numArgs > 1)
	{
		args[1]=arg2;
		if(numArgs > 2)
		{
			args[2]=arg3;
			if(numArgs > 3)
			{
				args[3]=arg4;
				if(numArgs > 4)
				{
					args[4]=arg5;
					if(numArgs > 5)
					{
						args[5]=arg6;
						if(numArgs > 6)
						{
							args[6]=arg7;
							if(numArgs > 7)
							{
								args[7]=arg8;
								if(numArgs > 8)
								{
									args[8]=arg9;
									if(numArgs > 9)
									{
										args[9]=arg10;
										if(numArgs > 10)
										{
											args[10]=arg11;
											if(numArgs > 11)
											{
												args[11]=arg12;
												if(numArgs > 12)
												{
													args[12]=arg13;
													if(numArgs > 13)
													{
														args[13]=arg14;
														if(numArgs > 14)
														{
															args[14]=arg15;
															if(numArgs > 15)
															{
																args[15]=arg16;
																if(numArgs > 16)
																{
																	args[16]=arg17;
																}
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	for(i=0; i<numArgs; i++)
	{
		args[i].TrimRight();
		args[i].TrimLeft();
		
		if(args[i].Find(_T(' ')) != -1 && args[i][0] != _T('\"'))
		{
			TCHAR buf[1024];
			lstrcpy(buf, args[i]);
			args[i].Format(_T("\"%s\""), buf);
		}
	}

	appName.TrimRight();
	appName.TrimLeft();
	if(appName.Find(_T(' ')) != -1 && appName[0] != _T('\"'))
	{
		TCHAR buf[1024];
		lstrcpy(buf, appName);
		appName.Format(_T("\"%s\""), buf);
	}

	// Pass the P4CHARSET to P4Merge.exe if its version is 2005.2 or later and have Unicode files
	CString appFileName;
	if ((i = appName.ReverseFind(_T('\\'))) != -1)
		appFileName = appName.Mid(i+1);
	else
		appFileName = appName;
	if (isUnicode && !appFileName.CollateNoCase(_T("P4Merge.exe")) && m_P4MergeVer >= 20052)
	{
		CString charset;
		if (isUnicode == 16)
			charset = _T("utf16");
		else
			charset = GET_P4REGPTR()->GetP4Charset();
		if (charset.GetLength() > 0)
			appName += _T(" -C ") + charset + _T(" ");
	}

	BOOL success;
	TCHAR commandLine[1024];
	CString tempFile;

	// Set up the command line
	CString commLine=appName;
	
	i = 0;
	if (app == DIFF_APP)
	{
		if (!GET_P4REGPTR()->GetDiffInternal()
		 &&  GET_P4REGPTR()->GetDiffAppOptArgChk() 
		 && *(GET_P4REGPTR()->GetDiffOptArgs()))
		{
			CString optArgs = GET_P4REGPTR()->GetDiffOptArgs();
			optArgs.Replace(_T("%1"), _T("%#1"));
			optArgs.Replace(_T("%2"), _T("%#2"));
			optArgs.Replace(_T("%L"), _T("%#L"));
			optArgs.Replace(_T("%R"), _T("%#R"));
			commLine += CString(_T(" ")) + optArgs;
			i =  commLine.Replace(_T("%#1"), args[0]);
			i *= commLine.Replace(_T("%#2"), args[1]);
			commLine.Replace(_T("%#L"), orgArgX);
			commLine.Replace(_T("%#R"), orgArgY);
			_stprintf(buf2, _T("%d"), GET_P4REGPTR()->GetTabWidth());
			commLine.Replace(_T("%W"), buf2);
		}
	}
	else if (app == MERGE_APP)
	{
		if (!GET_P4REGPTR()->GetMergeInternal()
		 &&  GET_P4REGPTR()->GetMergeAppOptArgChk() 
		 && *(GET_P4REGPTR()->GetMergeOptArgs()))
		{
			CString optArgs = GET_P4REGPTR()->GetMergeOptArgs();
			commLine += CString(_T(" ")) + optArgs;
			optArgs.Replace(_T("%1"), _T("%#1"));
			optArgs.Replace(_T("%2"), _T("%#2"));
			optArgs.Replace(_T("%3"), _T("%#3"));
			optArgs.Replace(_T("%4"), _T("%#4"));
			optArgs.Replace(_T("%T"), _T("%#T"));
			optArgs.Replace(_T("%Y"), _T("%#Y"));
			i =  commLine.Replace(_T("%1"), args[0]);
			i *= commLine.Replace(_T("%2"), args[1]);
			i *= commLine.Replace(_T("%3"), args[2]);
			i *= commLine.Replace(_T("%4"), args[3]);
			orgArgY.TrimLeft();
			commLine.Replace(_T("%T"), orgArgY);
			commLine.Replace(_T("%Y"), orgArgX);
			_stprintf(buf2, _T("%d"), GET_P4REGPTR()->GetMrgTabWidth());
			commLine.Replace(_T("%W"), buf2);
		}
	}
	if (!i)
	{
		for(i=0; i<numArgs; i++)
			commLine+=_T(" ")+args[i];
	}

	/* We have to do nasty things with the system settings to fool win/2k/xp/98 
	into bringing the correct window to the foreground after a spawn with wait 
	finishes. Starting with Windows 98 & Windows 2000 the OS restricts which 
	processes can set the foreground window. A process can set the 
	foreground window only if certain conditions are met - none of which 
	can be met after a spawn with wait finishes. Therefore change the 
	system setting that controls this new, useless behavior to disable it 
	before the spawn is done; then restore the previous settings after 
	the correct window has been brought to the foreground. Furthermore we
	have to use the actual value of SPI_*ETFOREGROUNDLOCKTIMEOUT rather
	than the defines because they are not defined on NT.
	*/

	DWORD dwLockTimeout = 0;
	SystemParametersInfo(0x2000 /*==SPI_GETFOREGROUNDLOCKTIMEOUT*/, NULL, &dwLockTimeout, NULL);
	if (dwLockTimeout && (mode == RA_WAIT))
		SystemParametersInfo(0x2001 /*==SPI_SETFOREGROUNDLOCKTIMEOUT*/, 0, (LPVOID)0, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);

	if(isConsole)
	{
		// Win95 command shell does not have a scrollable screen buffer,
		// so pipe diff results to more by using a batch file
		if((osVer.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && (app == DIFF_APP))
		{
	  		TCHAR tempPath[LONGPATH];
  			HANDLE h=0;

			commLine+=_T(" | more");

			if(GetTempPath(LONGPATH, tempPath) == 0)
  			{
  				errorText.FormatMessage(IDS_COULD_NOT_RUN_s_TEMP_COMMAND_PATH_ERROR, appText);
				if (dwLockTimeout && (mode == RA_WAIT))
					SystemParametersInfo(0x2001 /*==SPI_SETFOREGROUNDLOCKTIMEOUT*/, 0, (LPVOID)dwLockTimeout, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
  				return FALSE;
  			}

  			for(i=0; i<100; i++)
  			{
  				tempFile.Format(_T("%sP4W%05ld.BAT"), tempPath, i);
  				h=CreateFile(tempFile, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0);
  				if(h != INVALID_HANDLE_VALUE)
  					break;
  			}
  		
  			if(i==100)
  			{
  				errorText.FormatMessage(IDS_COULD_NOT_RUN_s_COULD_NOT_OPEN_TEMP_COMMAND_FILE, appText);
				if (dwLockTimeout && (mode == RA_WAIT))
					SystemParametersInfo(0x2001 /*==SPI_SETFOREGROUNDLOCKTIMEOUT*/, 0, (LPVOID)dwLockTimeout, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
  				return FALSE;
  			}

  			DWORD numWrite;
  			if(!WriteFile(h, commLine, lstrlen(commLine), &numWrite, 0))
  			{
  				CloseHandle(h);
  				errorText.FormatMessage(IDS_COULD_NOT_RUN_s_COULD_NOT_WRITE_TEMP_COMMAND_FILE, appText);
				if (dwLockTimeout && (mode == RA_WAIT))
					SystemParametersInfo(0x2001 /*==SPI_SETFOREGROUNDLOCKTIMEOUT*/, 0, (LPVOID)dwLockTimeout, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
  				return FALSE;
  			}

  			CloseHandle(h);

			commLine = tempFile;
		}
		else
		{
			TCHAR	cmd[MAX_PATH+1];

			GetEnvironmentVariable(_T("ComSpec"), cmd, MAX_PATH);
			commLine = (CString)cmd + (isClose ? _T(" /c start /wait ") : _T(" /k ")) + commLine;
		}

		lstrcpy(commandLine, commLine);

		SetLastError(0);
		success=CreateProcess( NULL,	// pointer to name of executable module 
					commandLine,	// pointer to command line string
					NULL, NULL,  // default security rot
					FALSE,	// handle inheritance flag 
					dwCreationFlags | CREATE_NEW_CONSOLE,	// creation flags 
					NULL, NULL, //default env and curdir
					&startInfo, &procInfo);
	}
	else
	{
		SetLastError(0);
		lstrcpy(commandLine, commLine);
		success=CreateProcess( NULL,	// pointer to name of executable module 
						commandLine,	// pointer to command line string
						NULL, NULL,  // default security rot
						FALSE,	// handle inheritance flag 
						dwCreationFlags,	// creation flags 
						NULL, // env
						NULL, //default dir
						&startInfo, &procInfo);					
	}

	if(success)
	{
		if(mode == RA_WAIT)
		{
			CString	oldtext;
			CString	newtext;
            newtext.FormatMessage(IDS_WAITING_FOR_s_TO_FINISH, appName);

			// Let the user know what we aren't responding
			//
			AfxGetMainWnd()->GetWindowText(oldtext);
			AfxGetMainWnd()->SetWindowText(newtext);

			// Disable the main window and any topmost dialog
			//
			BOOL bReEnableMain = app == MERGE_APP;
			if (AfxGetMainWnd()->IsWindowEnabled())
			{
				EnableWindow( AfxGetMainWnd()->GetSafeHwnd(), FALSE );
				bReEnableMain = TRUE;
			}
			BOOL bReEnable = FALSE;
			// if we weren't given a topmost dialog, see if we can figure it out
			if (!hWnd)
			{
				CWnd *pWnd= AfxGetMainWnd()->GetForegroundWindow();	// get the foreground window
				if (pWnd->GetWindow(GW_OWNER) == AfxGetMainWnd())	// make sure it's ours!
					hWnd = pWnd->GetSafeHwnd();
			}
			if (hWnd)
			{
				EnableWindow( hWnd, FALSE );
				bReEnable = TRUE;
			}
			::ShowWindow(MainFrame()->m_hWnd, SW_SHOWMINNOACTIVE);

			// Wait for the spawned app
			//
			while (WaitForSingleObject( procInfo.hProcess, 1000 ) == WAIT_TIMEOUT)
			{
				MSG msg;

				while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
				{
					if ( msg.message == WM_COMMAND )	// ignore all commands
						continue;
					if ( msg.message == WM_TIMER )		// Don't poll while waiting
						MainFrame()->WaitAWhileToPoll( );
					TranslateMessage(&msg);
					DispatchMessage(&msg);
					if ( msg.message == WM_QUIT )	// get out if app is terminating
						break;
				}
			}
			CloseHandle(procInfo.hProcess);

			// Re-enable the windows
			//
			if (bReEnable)
				::ShowWindow(hWnd, SW_RESTORE);
			if (bReEnableMain)
			{
				::EnableWindow( AfxGetMainWnd()->GetSafeHwnd(), TRUE );
				::SetForegroundWindow( AfxGetMainWnd()->GetSafeHwnd() );
				::SetFocus( AfxGetMainWnd()->GetSafeHwnd() );
			}
			if (bReEnable)
			{
				::EnableWindow( hWnd, TRUE );
				::SetForegroundWindow( hWnd );
				::SetFocus( hWnd );
			}
			AfxGetMainWnd()->SetWindowText(oldtext);
			::ShowWindow(MainFrame()->m_hWnd, SW_RESTORE);
			::SendMessage(MainFrame()->m_hWnd, m_WM_ACTIVATE, 0, 0);
		}
		else if (mode == RA_THREAD || mode == RA_THREADWAIT)
		{
			// Give the spawned program time to get initalized
			WaitForInputIdle(procInfo.hProcess, 1000);
			Sleep(1000);	// And since WaitForInputIdle() is not reliable, wait some more

			// Now fire up the thread which will delete the temp file(s);
			// this thread does WaitForSingleObject(procInfo.hProcess, INFINITE)
			// before deleting any files. It also calls CloseHandle(procInfo.hProcess)
			lprati->hProcess = procInfo.hProcess;
			CWinThread *pTaskThread=AfxBeginThread(lprati->pfnThreadProc, 
						(LPVOID) lprati, THREAD_PRIORITY_NORMAL, 
						0, CREATE_SUSPENDED, NULL);
			pTaskThread->m_bAutoDelete=TRUE;   // Tinker w/ priority here if reqd
			pTaskThread->ResumeThread();

			// If the mode is RA_THREADWAIT, wait until the spawned process exits
			if (mode == RA_THREADWAIT)
			{
				Sleep(333);
				ShowWindow(m_pMainWnd->m_hWnd, SW_HIDE);
				WaitForSingleObject( procInfo.hProcess, INFINITE );
				// Window is NOT re-shown because RA_THREADWAIT is only used
				// when P4Win is run with the -D [filename] flag.
				// P4Win will now exit since the Diff program has exited.
			}
		}
		else
			CloseHandle(procInfo.hProcess);

		// procInfo.hProcess was close by each of the 3 cases above, 
		// but we still must close procInfo.hThread.
		CloseHandle(procInfo.hThread);

		// Note: checking exit codes is futile.  
		//	CMD and COMMAND return random numbers, no matter what happens
	}
	else
	{
		CString errTxt;
		DWORD error=GetLastError();
		if(error)
		{
			LPVOID lpMsgBuf;
			FormatMessage( 
				FORMAT_MESSAGE_ALLOCATE_BUFFER | 
				FORMAT_MESSAGE_FROM_SYSTEM | 
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				error,
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
				(LPTSTR) &lpMsgBuf,
				0,
				NULL 
			);
			errTxt = (TCHAR *)lpMsgBuf;
			errTxt.TrimRight();
		}
		else
			errTxt=LoadStringResource(IDS_UNKNOWN_ERROR);
		errorText.FormatMessage(IDS_FAILED_TO_EXECUTE_s_s_s_n, appText, appName, errTxt, error);
		if (commLine.GetLength() > 127)
		{
			// Find out if we're running NT or win95
			OSVERSIONINFO osVer;
			osVer.dwOSVersionInfoSize= sizeof(OSVERSIONINFO);
			GetVersionEx(&osVer);
			BOOL brittleWare= (osVer.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);

    		if(brittleWare)
				errTxt.FormatMessage(IDS_s_COMMAND_LINE_TOO_LONG_FOR_WIN9X_s_n, 
										appText, commLine, commLine.GetLength());
			else
				errTxt.FormatMessage(IDS_CHECK_PERFORCE_OPTIONS_s, appText);
		}
		else
			errTxt.FormatMessage(IDS_CHECK_PERFORCE_OPTIONS_s, appText);
		errorText += errTxt;
	}

	// restore previous system setting (see comment above,
	// at 1st call to SystemParametersInfo(), for details)
	if (dwLockTimeout && (mode == RA_WAIT))
		SystemParametersInfo(0x2001 /*==SPI_SETFOREGROUNDLOCKTIMEOUT*/, 0, (LPVOID)dwLockTimeout, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);

	if(isConsole && !tempFile.IsEmpty())
  		_tunlink(tempFile);
	return success;
}


// Spawn a viewer. App is referenced by name, and console apps will not be catered to.
// This is a stripped down version of CP4WinApp::RunApp()
BOOL CP4winApp::RunViewerApp(LPCTSTR app, LPCTSTR fileName)
{
	if( app==NULL || lstrlen(app) == 0 ||
		fileName==NULL || lstrlen(fileName) == 0)
	{
		// Dont ASSERT(0); since this error occurrs for any binary file w/ no associated app
		return FALSE;
	}

	// Set up the spawn operation
	PROCESS_INFORMATION procInfo;
	STARTUPINFO startInfo;
	
	GetStartupInfo(&startInfo);
	startInfo.lpReserved= startInfo.lpDesktop= NULL; 
	startInfo.dwFlags |= STARTF_USESHOWWINDOW;
	startInfo.wShowWindow = SW_SHOWNORMAL;

	CString arg=fileName;
	arg.TrimRight();
	arg.TrimLeft();
		
	if(arg.Find(_T(' ')) != -1 && arg[0] != _T('\"'))
	{
		TCHAR buf[1024];
		lstrcpy(buf, arg);
		arg.Format(_T("\"%s\""), buf);
	}


	CString appName=app;
	appName.TrimRight();
	appName.TrimLeft();
	if(appName.Find(_T(' ')) != -1 && appName[0] != _T('\"'))
	{
		TCHAR buf[1024];
		lstrcpy(buf, appName);
		appName.Format(_T("\"%s\""), buf);
	}
			
	// Set up the command line
	CString commLine=appName;
	commLine+=_T(" ")+arg;
	
	BOOL success=CreateProcess( NULL,	// pointer to name of executable module 
						const_cast<LPTSTR>((LPCTSTR)commLine),	// pointer to command line string
						NULL, NULL,  // default security rot
						FALSE,	// handle inheritance flag 
#ifdef UNICODE
						NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, 
#else
						NORMAL_PRIORITY_CLASS,	// creation flags 
#endif
						NULL, NULL, //default env and curdir
						&startInfo, &procInfo);					
	
	if(success)
	{
		// Note:  checking exit codes is futile.  CMD and COMMAND return random numbers, no
		//        matter what happens
		CloseHandle(procInfo.hProcess);
		CloseHandle(procInfo.hThread);
	}

	return success;
}


/////////////////////////////////////////////////////////////////////////////////
// Minor text formatting functions used throughout app, so multiline edit boxes 
// can translate text with embedded tabs, as generated by command line clients

// Utility function to remove tabs and replace all LF's with CR-LFs
CString RemoveTabs(LPCTSTR text)
{
	SET_BUSYCURSOR();
	CString out=text;	// avoid billions of re-allocs by setting approx length
	out+=text;			// then double the length of the buffer
	out="";				// and empty the buffer without freeing it

	int i=0;
	
	while(text[i] != _T('\0'))
	{
		if(text[i]!=_T('\t'))
		{
			if(text[i]==_T('\n') && text[max(0,i-1)]!=_T('\r'))
				out += _T("\r\n");
			else
				out += text[i];
		}

		i++;
	}
	
	// Strip off a trailing newline char
	if(out.GetLength() > 0 && out[out.GetLength()-1] == _T('\n'))
		out=out.Left(out.GetLength()-2);

	return out;
}


CString MakeCRs(LPCTSTR text)
{
    // replace LF with CR-LF
    // then strip any trailing CR-LFs

    int size = lstrlen(text) + 1;
    CString out;
    LPTSTR pOutBuf = out.GetBufferSetLength(size);
    LPTSTR pOut = pOutBuf;
    // keep track of where any trailing CRs are located so they can be chopped
    // without repeatedly scanning the string (which may be very long)
    // this pointer points to the char following the last non-CR character
    LPTSTR pLastNonCR = pOut;
    // there will be probably be a final realloc, and this is probably a temp 
    // string anyway, so minimize the number of reallocs rather than the size
    const int growBy = 4096;    

#ifdef UNICODE
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn++)
#else
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn = CharNext(pIn))
#endif
	{
        // make sure there will be enough room for a character
        // even if it is a double byte char, or an expanded CR
        if(pOut - pOutBuf + 2 > size - 1)
        {
            int oldSize = pOut - pOutBuf;
            int nLastNonCR = pLastNonCR - pOutBuf;
            out.ReleaseBuffer(oldSize);
            size += growBy;
            pOutBuf = out.GetBufferSetLength(size);
            pOut = pOutBuf + oldSize;
            pLastNonCR = pOutBuf + nLastNonCR;
        }
		if(*pIn==_T('\r'))
        {
            *pOut++ = *pIn;
            if(pIn[1] == _T('\n'))
                *pOut++ = *++pIn;
            else
                pLastNonCR = pOut;
        }
        else if(*pIn==_T('\n'))
        {
            // a LF with no preceding CR, so insert a CR
            *pOut++ = _T('\r');
            *pOut++ = _T('\n');
        }
		else
        {
#ifdef UNICODE
			*pOut++ = *pIn;
#else
			_tccpy(pOut, pIn);
            pOut = CharNext(pOut);
#endif
            pLastNonCR = pOut;
        }
	}
    // chop off any trailing newline chars
    *pLastNonCR = 0;
    out.ReleaseBuffer();
	
	return out;
}

CString MakeLFs(LPCTSTR text)
{
    // replace CR with CR-LF
    // then strip any trailing CR-LFs

    int size = lstrlen(text) + 1;
    CString out;
    LPTSTR pOutBuf = out.GetBufferSetLength(size);
    LPTSTR pOut = pOutBuf;
    // keep track of where any trailing CRs are located so they can be chopped
    // without repeatedly scanning the string (which may be very long)
    // this pointer points to the char following the last non-CR character
    LPTSTR pLastNonCR = pOut;
    // there will be probably be a final realloc, and this is probably a temp 
    // string anyway, so minimize the number of reallocs rather than the size
    const int growBy = 4096;    

#ifdef UNICODE
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn++)
#else
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn = CharNext(pIn))
#endif
	{
        // make sure there will be enough room for a character
        // even if it is a double byte char, or an expanded CR
        if(pOut - pOutBuf + 2 > size - 1)
        {
            int oldSize = pOut - pOutBuf;
            int nLastNonCR = pLastNonCR - pOutBuf;
            out.ReleaseBuffer(oldSize);
            size += growBy;
            pOutBuf = out.GetBufferSetLength(size);
            pOut = pOutBuf + oldSize;
            pLastNonCR = pOutBuf + nLastNonCR;
        }
		if(*pIn==_T('\r'))
        {
            *pOut++ = _T('\r');
            *pOut++ = _T('\n');
        }
		else
        {
#ifdef UNICODE
			*pOut++ = *pIn;
#else
            _tccpy(pOut, pIn);
            pOut = CharNext(pOut);
#endif
            pLastNonCR = pOut;
        }
	}
    // chop off any trailing newline chars
    *pLastNonCR = 0;
    out.ReleaseBuffer();
	
	return out;
}

CString UnMakeCRs(LPCTSTR text)
{
    // remove any CR characters
    // then strip any trailing LFs

    int size = lstrlen(text) + 1;
    CString out;
    LPTSTR pOut = out.GetBufferSetLength(size);
    // keep track of where any trailing LFs are located so they can be chopped
    // without repeatedly scanning the string (which may be very long)
    // this pointer points to the char following the last non-LF character
    LPTSTR pLastNonLF = pOut;

#ifdef UNICODE
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn++)
#else
	for(LPCTSTR pIn = text; *pIn != _T('\0'); pIn = CharNext(pIn))
#endif
	{
        if(*pIn != _T('\r'))
        {
            if(*pIn == _T('\n'))
                *pOut++ = *pIn;
            else
            {
#ifdef UNICODE
			*pOut++ = *pIn;
#else
                _tccpy(pOut, pIn);
                pOut = CharNext(pOut);
#endif
                pLastNonLF = pOut;
            }
        }
	}
    // chop off any trailing newline chars
    *pLastNonLF = 0;
    out.ReleaseBuffer();
	
	return out;
}

CString PadCRs(LPCTSTR text)
{
	CString out;
		
	// Replace CR and TABs with spaces.  This allows multi-line descriptions
	// to be displayed on a single line
	int len=lstrlen(text);
	LPCTSTR ptr=text;
	LPTSTR ptrout=out.GetBuffer(len);
	LPTSTR ptrstart = ptrout;
	for(int i=0; i<len; i++)
	{
		if ((*ptr==_T('\r')) || (*ptr==_T('\n')) || (*ptr==_T('\t')))
		{
			if ((ptrout > ptrstart) && (*(ptrout-1) != _T(' ')))
				*ptrout++ = _T(' ');
			ptr++;
		}
		else
		{
			*ptrout++ = *ptr++;
		}
	}
	
	*ptrout=_T('\0');
	out.ReleaseBuffer();
	
	return out;
}

CString WrapDesc(LPCTSTR text, int maxcol)
{
	int count = 0;
    int size = lstrlen(text) + 1;
    CString out;
	CString savestr;
    LPTSTR pOut = out.GetBufferSetLength(size*2);
	LPTSTR pLastWhite = pOut;
	LPTSTR pLWnext = pOut;
	LPTSTR pBgnLine = pOut;
	LPTSTR p;
	for(LPCTSTR pIn = text; *pIn != _T('\0'); )
	{
		if ((*pIn == _T('\r')) || (*pIn == _T('\n')))
		{
			count = 0;
			pLastWhite = pBgnLine = pOut;
		}
		if (*pIn <= _T(' '))
			pLWnext = pOut;
#ifdef UNICODE
		*pOut++ = *pIn++;
#else
		_tccpy(pOut, pIn);
		pOut = CharNext(pOut);
		pIn = CharNext(pIn);
#endif
		if ((++count > maxcol) && (pLastWhite != pBgnLine) 
			&& (*pIn != _T('\r')) && (*pIn != _T('\n')))
		{
			*pOut = _T('\0');
#ifdef UNICODE
			pLastWhite++;
#else
			pLastWhite = CharNext(pLastWhite);
#endif
			savestr = pLastWhite;
			*pLastWhite++ = _T('\r');
			*pLastWhite++ = _T('\n');
			lstrcpy(pLastWhite, savestr);
			pOut = pLastWhite + lstrlen(pLastWhite);
			pLWnext = pBgnLine = pLastWhite;
			for (count = 0, p = pBgnLine; p < pOut; )
			{
#ifdef UNICODE
				p++;
#else
				p = CharNext(p);
#endif
				if ((*p == _T('\r')) || (*p == _T('\n')))
				{
					count = 0;
					pBgnLine = p;
				}
				else
					count++;
				if (*p <= _T(' ') && *p)
					pLWnext = p;
			}
		}
		pLastWhite = pLWnext;

	}
	*pOut = _T('\0');
    out.ReleaseBuffer();
	
	return out;
}

/////////////////////////////////////////////////
// Utility functions to perform string comparisons according
// to the case-sensitivity of the server

int Compare(LPCTSTR str1, LPCTSTR str2)
{
	// If server level not set, we also dont know if server is nocase
	ASSERT(GET_SERVERLEVEL());

	if(IS_NOCASE())
		return _tcsicmp(str1, str2);
	else
		return _tcscmp(str1, str2);
}

int nCompare(LPCTSTR str1, LPCTSTR str2, int n)
{
	// If server level not set, we also dont know if server is nocase
	ASSERT(GET_SERVERLEVEL());

	if(IS_NOCASE())
		return _tcsnicmp(str1, str2, n);
	else
		return _tcsncmp(str1, str2, n);
}

int nCommon(LPCTSTR str1, LPCTSTR str2)
{
	// If server level not set, we also dont know if server is nocase
	ASSERT(GET_SERVERLEVEL());

	int commonLength=0;
	LPCTSTR ptr1= str1;
	LPCTSTR ptr2= str2;

	if(IS_NOCASE())
	 	while( (TBYTE)CharUpper((LPTSTR)(TBYTE)*ptr1) == (TBYTE)CharUpper((LPTSTR)(TBYTE)*ptr2) && 
            *ptr1 != _T('\0') && *ptr2 != _T('\0'))
		{		
			commonLength++;
			ptr1++;
			ptr2++;
		}	
	else
		while( *ptr1 == *ptr2 && *ptr1 != _T('\0') && *ptr2 != _T('\0'))
		{		
			commonLength++;
			ptr1++;
			ptr2++;
		}
	return commonLength;
}

void TrimRightMBCS(CString &str, TCHAR *chars)
{
#ifdef UNICODE
	str.TrimRight(chars);
#else
	int len;
	LPTSTR pBuf = str.GetBuffer(len = str.GetLength());
	TCHAR *pchBgn = (TCHAR *)pBuf;
	TCHAR *pchLst = _tcsdec(pchBgn, pchBgn + len);
	BOOL b = TRUE;
	while (b)
	{
		b = FALSE;
		for (TCHAR *pchChk = chars; *pchChk; pchChk = _tcsinc(pchChk))
		{
			if (*pchChk == *pchLst)
			{
				*pchLst = _T('\0');
				pchLst = _tcsdec(pchBgn, pchLst);
				b = TRUE;
				break;
			}
		}
	}
	str.ReleaseBuffer(-1);
#endif
}

// This functions assumes that oldchar and newchar are the same width!
// Since this is used to replace \ with /, this works fine.
void ReplaceMBCS(CString &str, TCHAR oldchar, TCHAR newchar)
{
#ifdef UNICODE
	str.Replace(oldchar, newchar);
#else
#ifdef _DEBUG
	TCHAR oldbuf[8];
	TCHAR newbuf[8];
	memset(oldbuf, _T('\0'), sizeof(oldbuf));
	memset(newbuf, _T('\0'), sizeof(newbuf));
	oldbuf[0] = oldchar;
	newbuf[0] = newchar;
	ASSERT(lstrlen(oldbuf) == lstrlen(newbuf));
#endif

	LPTSTR pBuf = str.GetBuffer(str.GetLength());
	for (TCHAR *pch = (TCHAR *)pBuf; *pch; pch = _tcsinc(pch))
	{
		if (*pch == oldchar)
			*pch =  newchar;
	}
	str.ReleaseBuffer(-1);
#endif
}

int FindMBCS(CString &str, TCHAR findchar, int skip /*=0*/)
{
#ifdef UNICODE
	return str.Find(findchar, skip);
#else
	ASSERT(skip >= 0);

	int i;
	LPTSTR pBuf = str.GetBuffer(str.GetLength());
	TCHAR *pchBgn = (TCHAR *)pBuf;
	TCHAR *pch;
	for (i=-1, pch=pchBgn; *pch; pch = _tcsinc(pch))
	{
		if (skip)
		{
			skip--;
		}
		else if (*pch == findchar)
		{
			i = pch - pchBgn;
			break;
		}
	}
	str.ReleaseBuffer(-1);
	return i;
#endif
}

int ReverseFindMBCS(CString &str, TCHAR findchar)
{
#ifdef UNICODE
	return str.ReverseFind(findchar);
#else
	int len;
	LPTSTR pBuf = str.GetBuffer(len = str.GetLength());
	TCHAR *pchBgn = (TCHAR *)pBuf;
	TCHAR *pchLst = _tcsdec(pchBgn, pchBgn + len);
	while (pchBgn < pchLst)
	{
		if (*pchLst == findchar)
			break;
		pchLst = _tcsdec(pchBgn, pchLst);
	}
	if (pchBgn < pchLst)
		len = pchLst - pchBgn;
	else
		len = *pchBgn == findchar ? 0 : -1;
	str.ReleaseBuffer(-1);
	return len;
#endif
}

// Function to get a file's extension
CString GetFilesExtension(LPCTSTR filename)
{
	CString extension = filename;
	int slash= extension.ReverseFind(_T('\\'));
	if(slash != -1)
		extension=extension.Mid(slash+1);

	int dot= extension.ReverseFind(_T('.'));
	if(dot == -1)
		extension.Empty();
	else
		extension=extension.Mid(dot+1);
	return extension;
}

// Function to compare 2 path&filenames in MS
// TreeView order - which is definitely weird
// (see comment below). This means x.ext comes
// before x-y.ext even tho - comes before .
int fCompare(LPCTSTR str1, LPCTSTR str2, BOOL ext1st/*=FALSE*/)
{
	CString bas1 = str1;
	CString bas2 = str2;
	// Since we do our own sorting when we are in "sort by extension"
	// we don't have to do stupid win32 stuff
	if (ext1st)
	{
		int rc;
		CString ext1 = GetFilesExtension(str1);
		CString ext2 = GetFilesExtension(str2);
		if ((rc = ext1.CompareNoCase(ext2)) == 0)
			 rc = bas1.CompareNoCase(bas2);
		return rc;
	}
	// In general, the Win32 sort order is this: 
	//		Non-alpha-numeric (punctuation) characters in ASCII or ANSI order 
	//		Numeric characters in numeric order 
	//		Alphabetic characters in case-insensitive alphabetic order 
	// For Win32 it is the same, with two exceptions: 
	// the hyphen or minus (-) symbol and the single-quote or apostrophe ( ' ). 
	// These two characters are ignored when sorting strings because they are 
	// allowed to be embedded in English-language words. For example, "its" and
	// "it's" and "co-op" and "coop." The presence of these characters embedded
	// in words causes certain searches to break incorrectly, so they are changed
	// to be treated just like other diacritical marks embedded in text strings;
	// that is, they're ignored.  These rather remarkable words are from
	// ms-help://MS.VSCC/MS.MSDNVS/dnaskdr/html/drgui49.htm and
	// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaskdr/html/drgui49.asp
	bas1.Remove(_T('\''));
	bas1.Remove(_T('-'));
	bas2.Remove(_T('\''));
	bas2.Remove(_T('-'));
	return bas1.CompareNoCase(bas2);
}

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

void CopyTextToClipboard(LPCTSTR txt)
{
	if( lstrlen(txt) > 0 )
	{
		COleDataSource *pSource= new COleDataSource();
		HGLOBAL hText= ::GlobalAlloc(GMEM_SHARE, (lstrlen(txt)+1)*sizeof(TCHAR));
		LPTSTR pStr= (LPTSTR) ::GlobalLock( hText );
		lstrcpy( pStr, txt );
		::GlobalUnlock( hText );
	#ifdef UNICODE
		pSource->CacheGlobalData( CF_UNICODETEXT, hText );
	#else
		pSource->CacheGlobalData( CF_TEXT, hText );
	#endif
		pSource->SetClipboard();
	}
}

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

void CP4winApp::OnUpdateNewWindow(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(MainFrame()->IsModlessUp() ? FALSE : !SERVER_BUSY());
}

void CP4winApp::OnNewWindow() 
{
	if (MainFrame()->IsModlessUp())
		return;

	CNewWindowDlg newWindowDlg;
	newWindowDlg.DoModal();
}


void CP4winApp::GetFileType(const CString &itemStr, int &BaseType, int &StoreType,
							BOOL &TypeK, BOOL &TypeW, BOOL &TypeX, BOOL &TypeO,
							BOOL &TypeM, BOOL &TypeL, BOOL &TypeS, int &NbrRevs, BOOL &Unknown)
{
	TypeS = FALSE;

	// shortcut for file not in depot
	if(itemStr.Find(_T(" <")) == -1)
	{
		BaseType = -1;
		StoreType = -1;
		Unknown = FALSE;
		return;
	}

	int		plus;
	TCHAR	c;

	if (itemStr.Find(_T(" <text")) != -1)
		BaseType = 0;
	else if (itemStr.Find(_T(" <binary")) != -1)
		BaseType = 1;
	else if (itemStr.Find(_T(" <symlink")) != -1)
		BaseType = 2;
	else if (itemStr.Find(_T(" <resource")) != -1)
		BaseType = 3;
	else if (itemStr.Find(_T(" <apple")) != -1)
		BaseType = 4;
	else if (itemStr.Find(_T(" <unicode")) != -1)
		BaseType = 5;
	else if (itemStr.Find(_T(" <utf16")) != -1)
		BaseType = 6;

	StoreType = 0;
	Unknown = FALSE;

	if ((plus = itemStr.Find(_T("+"), itemStr.Find(_T(" <")))) != -1)
	{
		while (((c = itemStr.GetAt(++plus)) != _T('>')) && c)
		{
			switch (c)
			{
			case _T('x'):
				TypeX = TRUE;
				break;

			case _T('w'):
				TypeW = TRUE;
				break;

			case _T('k'):
				TypeK = TRUE;
				break;

			case _T('o'):
				TypeO = TRUE;
				TypeK = TRUE;
				break;

			case _T('m'):
				TypeM = TRUE;
				break;

			case _T('l'):
				TypeL = TRUE;
				break;

			case _T('C'):
				StoreType = 1;
				break;

			case _T('D'):
				StoreType = 2;
				break;

			case _T('F'):
				StoreType = 3;
				break;

			case _T('S'):
				TypeS = TRUE;
				if (_istdigit(itemStr.GetAt(plus+1)))
				{
					CString str = itemStr.Mid(plus+1);
					NbrRevs = (int)_tstof(str);
					if (NbrRevs < 1)
						NbrRevs = 1;
					while (_istdigit(itemStr.GetAt(++plus)))
						;
					plus--;
				}
				else
					NbrRevs = 1;
				break;

			default:
				Unknown = TRUE;
				break;
			}
		}
	}
	else if (itemStr.Find(_T(" <xtext>")) != -1)
	{
		BaseType = 0;
		TypeX = TRUE;
	}
	else if (itemStr.Find(_T(" <ktext>")) != -1)
	{
		BaseType = 0;
		TypeK = TRUE;
	}
	else if (itemStr.Find(_T(" <kxtext>")) != -1)
	{
		BaseType = 0;
		TypeX = TRUE;
		TypeK = TRUE;
	}
	else if (itemStr.Find(_T(" <xbinary>")) != -1)
	{
		BaseType = 1;
		TypeX = TRUE;
	}
	else if (itemStr.Find(_T(" <ctext>")) != -1)
	{
		BaseType = 0;
		StoreType = 1;
	}
	else if (itemStr.Find(_T(" <cxtext>")) != -1)
	{
		BaseType = 0;
		StoreType = 2;
		TypeX = TRUE;
	}
	else if (itemStr.Find(_T(" <ltext>")) != -1)
	{
		BaseType = 0;
		StoreType = 3;
	}
	else if (itemStr.Find(_T(" <lxtext>")) != -1)
	{
		BaseType = 0;
		TypeS = TypeX = TRUE;
	}
	else if (itemStr.Find(_T(" <ubinary>")) != -1)
	{
		BaseType = 1;
		StoreType = 3;
	}
	else if (itemStr.Find(_T(" <tempobj>")) != -1)
	{
		BaseType = 1;
		TypeS = TypeW = TRUE;
	}
	else if ((itemStr.Find(_T(" <xtempobj>")) != -1)
		  || (itemStr.Find(_T(" <tempxobj>")) != -1))
	{
		BaseType = 1;
		TypeS = TypeW = TypeX = TRUE;
	}
	else if (itemStr.Find(_T(" <xunicode>")) != -1)
	{
		BaseType = 5;
		TypeX = TRUE;
	}
}

/*
	_________________________________________________________________
*/

LPCTSTR CP4winApp::GetClientSpecField( LPCTSTR fieldname, LPCTSTR spectext )
{
	static CString field;

	int start, end;

	field.Empty();
    CString spec= spectext;
    int lgth = spec.GetLength();

    for( start=end=0; spec[end] && end < lgth; end++)
    {
        if( spec[end] == _T('\n') )
        {
			CString line= spec.Mid( start, end-start+1 );
			start= end+1;

			if( line.Find(fieldname) == 0 )
			{
				int i;
				if ((i = line.Find(_T('\t'))) != -1)
				{
					field = line.Mid( line.Find(_T('\t')) + 1 );
				}
				else if ((i = spec.Find(_T("\n\n"), start)) != -1)
				{
					field = spec.Mid(start, i-start);
					field.TrimLeft();
				}
				field.TrimRight();
				break;
			}
		}
	}

	return field;
}

void AddToStatus(LPCTSTR txt, StatusView level, bool showDialog)
{
	if(AfxGetThread() == (CWinThread*) AfxGetApp())
		MainFrame()->AddToStatusLog(txt, level, showDialog);
	else
		TheApp()->StatusAdd(txt, level, showDialog);
}

BOOL CP4winApp::OnIdle(LONG lCount) 
{
	// if lCount is 0, this is the first call since going idle
	// therefore set a new idle flag so that the toolbar button
	// activate-or-not functions will process the selection list again
	//
	// We use += 2 to keep m_IdleCounter odd so that it will never
	// roll over and become 0 - which is our flag to indicate that
	// we are Not in the idle loop.
	//
	if (!lCount)
		m_IdleCounter += 2;
	m_IdleFlag = m_IdleCounter;

	BOOL rc = CP4GuiApp::OnIdle(lCount);

	m_IdleFlag = 0;
	return rc;
}

BOOL CP4winApp::CallP4RevisionTree(CString filepath)
{
	CString errorText;
	CString client = GET_P4REGPTR()->GetP4Client();
	CString port = GET_P4REGPTR()->GetP4Port();
	CString user = GET_P4REGPTR()->GetP4User();
	CString password= GET_P4REGPTR()->GetP4UserPassword();
	CString charset = GET_P4REGPTR()->GetP4Charset();
	CString winhandle;
	winhandle.Format(_T("%d"), MainFrame()->m_hWnd);
	CString cmd;
	cmd.Format(_T("\"tree %s\""), filepath);

	int i = 0;
	LPCTSTR argptr[10];

	if (password.GetLength() > 0)
	{
		argptr[i++] = _T("-P");
		argptr[i++] = password;
	}
	if (charset.GetLength() > 0)
	{
		argptr[i++] = _T("-C");
		argptr[i++] = charset;
	}
	if(MainFrame()->HaveP4QTree())
	{
		argptr[i++] = _T("-win");
		argptr[i++] = winhandle;
		argptr[i++] = _T("-cmd");
		argptr[i++] = cmd;
	}
	else
	{
		argptr[i++] = _T("-j");
		argptr[i++] = filepath;
	}
	while (i < 10)
		argptr[i++] = NULL;
	if (!TheApp()->RunApp(TREE_APP, RA_NOWAIT, AfxGetApp( )->m_pMainWnd->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], argptr[8]))
	{
		AddToStatus( LoadStringResource(IDS_UNABLETORUNREVTREE), SV_WARNING );
		return FALSE;
	}
	return TRUE;
}

BOOL CP4winApp::CallP4A(CString annpath, CString logpath, int revnbr)
{
	CString errorText;
	CString rev;
	CString client = GET_P4REGPTR()->GetP4Client();
	CString port = GET_P4REGPTR()->GetP4Port();
	CString user = GET_P4REGPTR()->GetP4User();
	CString password= GET_P4REGPTR()->GetP4UserPassword();
	CString charset = GET_P4REGPTR()->GetP4Charset();
	CString winhandle;
	winhandle.Format(_T("%d"), MainFrame()->m_hWnd);

	int i = 0;
	LPCTSTR argptr[10];

	if (password.GetLength() > 0)
	{
		argptr[i++] = _T("-P");
		argptr[i++] = password;
	}

	if (charset.GetLength() > 0)
	{
		argptr[i++] = _T("-C");
		argptr[i++] = charset;
	}

	if (MainFrame()->HaveTLV())
	{
		argptr[i++] = _T("-win");
		argptr[i++] = winhandle;
		argptr[i++] = _T("-cmd");
		CString cmd;
		cmd.Format(_T("\"%s %s\""), (GET_P4REGPTR()->GetTLVIncInteg() 
								  && MainFrame()->GetP4Vver() >= 20052)
						? _T("annotate -i") : _T("annotate"), annpath);	// command passed to V
		argptr[i++] = cmd;
		while (i < 10)
			argptr[i++] = NULL;
		if (!TheApp()->RunApp(ANNOTATE_APP, RA_NOWAIT, 
					AfxGetApp( )->m_pMainWnd->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], argptr[8]))
		{
			AddToStatus( LoadStringResource(IDS_UNABLETORUNP4A), SV_WARNING );
			return FALSE;
		}
	}
	return TRUE;
}

// Since client roots are entered by the user,
// they can appear in all sorts of weird shapes and forms.
// So always use this routune to set m_ClientRoot
// and clean up any messes afterwards.
BOOL CP4winApp::Set_m_ClientRoot(LPCTSTR clientroot)
{
	m_ClientRoot = clientroot;

	// handle client with multiple roots 
	// by running p4 info to get the real root
	int i;
	if ((i = m_ClientRoot.FindOneOf(_T("\r\n\t"))) != -1)
	{
		BOOL b = FALSE;
		CCmd_Info cmd;
		cmd.Init( NULL, RUN_SYNC );
		if( cmd.Run( ) && !cmd.GetError() )
		{
			CP4Info const &info = cmd.GetInfo();
			if (!info.m_ClientRoot.IsEmpty( ))
			{
				m_ClientRoot = info.m_ClientRoot;
				b = TRUE;
			}
		}
		if (!b)
			m_ClientRoot = m_ClientRoot.Left(i);
	}

	m_ClientRoot.Replace(_T('/'), _T('\\'));
	m_ClientRoot.TrimLeft(_T('\"'));
	m_ClientRoot.TrimRight(_T("\"\\"));
	switch(m_ClientRoot.GetLength())
	{
	case 0:
		m_ClientRoot = _T("\\");
		break;
	case 1:
		ASSERT(0);
		return FALSE;
	case 2:
		if (m_ClientRoot.GetAt(1) == _T(':'))
			m_ClientRoot += _T('\\');
		break;
	}
	return TRUE;
}

BOOL CP4winApp::Set_m_ClientSubOpts(LPCTSTR clientSubOpts)
{
	m_ClientSubOpts = 0;

	if (clientSubOpts)
	{
		if (_tcsstr(clientSubOpts, _T("revert")))
			m_ClientSubOpts = REVERTUNCHANGED;
		else if (_tcsstr(clientSubOpts, _T("leave")))
			m_ClientSubOpts = LEAVEUNCHANGED;
		else if (_tcsstr(clientSubOpts, _T("submit")))
			m_ClientSubOpts = SUBMITUNCHANGED;

		if (m_ClientSubOpts && _tcsstr(clientSubOpts, _T("reopen")))
			m_ClientSubOpts += REOPEN_MASK;
	}

	return m_ClientSubOpts > 0;
}

BOOL CP4winApp::digestIsSame(CP4FileStats *fs, BOOL retIfNotExist/*=FALSE*/, 
							 void *clientPtr/*=NULL*/)
{
	if (fs->GetDigest().IsEmpty())	// prior to 2005.1, we don't have the digest;
		return TRUE;				// so revert to old behavior

	Error e;
	CP4Command *pcmd = NULL;
	CGuiClient *client = (CGuiClient *)clientPtr;

	if (!client)
	{
		pcmd = new CP4Command;
		client = pcmd->GetClient();
		client->SetTrans();
		client->Init(&e);
		if( e.Test() )
		{
			delete pcmd;
			return FALSE;
		}
	}

	FileSysType ft;
	if (fs->IsTextFile())
		ft = (fs->GetHeadType().Find(_T("unicode")) != -1) ? FST_UNICODE 
		   : (fs->GetHeadType().Find(_T("utf16")) != -1) ? FST_UTF16 : FST_TEXT;
	else if (fs->GetHeadType().Find(_T("apple")) != -1)
		ft = FST_APPLETEXT;
	else if (fs->GetHeadType().Find(_T("resource")) != -1)
		ft = FST_RESOURCE;
	else if (fs->GetHeadType().Find(_T("symlink")) != -1)
		ft = FST_SYMLINK;
	else
		ft = FST_BINARY;
	FileSys *f = FileSys::Create( ft );
	if( e.Test() )
	{
		if (pcmd) delete pcmd;
		return FALSE;
	}

	StrBuf clientPath;
	clientPath << CharFromCString(fs->GetFullClientPath());
	f->Set(clientPath);

	StrBuf md5;
	f->Digest(&md5, &e);
	delete f;
	if (pcmd) delete pcmd;
    if( e.Test() )
		return retIfNotExist;
	return CharToCString(md5.Value()) == fs->GetDigest();
}

BOOL CP4winApp::localDigest(CP4FileStats *fs, CString *digest, BOOL retIfNotExist/*=FALSE*/, 
							 void *clientPtr/*=NULL*/)
{
	Error e;
	CP4Command *pcmd = NULL;
	CGuiClient *client = (CGuiClient *)clientPtr;
	*digest = _T("");

	if (!client)
	{
		pcmd = new CP4Command;
		client = pcmd->GetClient();
		client->SetTrans();
		client->Init(&e);
		if( e.Test() )
		{
			delete pcmd;
			return FALSE;
		}
	}

	FileSysType ft;
	if (fs->IsTextFile())
		ft = (fs->GetHeadType().Find(_T("unicode")) != -1) ? FST_UNICODE 
		   : (fs->GetHeadType().Find(_T("utf16")) != -1) ? FST_UTF16 : FST_TEXT;
	else if (fs->GetHeadType().Find(_T("apple")) != -1)
		ft = FST_APPLETEXT;
	else if (fs->GetHeadType().Find(_T("resource")) != -1)
		ft = FST_RESOURCE;
	else if (fs->GetHeadType().Find(_T("symlink")) != -1)
		ft = FST_SYMLINK;
	else
		ft = FST_BINARY;
	FileSys *f = FileSys::Create( ft );
	if( e.Test() )
	{
		if (pcmd) delete pcmd;
		return FALSE;
	}

	StrBuf clientPath;
	clientPath << CharFromCString(fs->GetFullClientPath());
	f->Set(clientPath);

	StrBuf md5;
	f->Digest(&md5, &e);
	delete f;
	if (pcmd) delete pcmd;
    if( e.Test() )
		return retIfNotExist;
	*digest = CharToCString(md5.Value());
	return TRUE;
}

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

int GetNbrNL(const CString *str)
{
	int i = 0;
	int n = 0;
	while ((i = str->Find(_T('\n'), i)) != -1)
	{
		n++;
		i++;
	}
	return n;
}

CImageList * CP4winApp::GetImageList() 
{ 
	return m_viewImageList; 
}

void CP4winApp::OnSysColorChange()
{
	// Make sure image lists gets a new background color
	m_viewImageList->OnSysColorChange(::GetSysColor(COLOR_WINDOW));
	m_toolBarImageList->OnSysColorChange(::GetSysColor(COLOR_3DFACE));
}

// Use the shell to display a "Choose Directory" dialog box for the user.
CString CP4winApp::BrowseForFolder(HWND hWnd, LPCTSTR startat, LPCTSTR lpszTitle, UINT nFlags)
{
	startingfolder = startat;
	CString strResult = _T("");

	LPMALLOC lpMalloc;  // pointer to IMalloc
	if (::SHGetMalloc(&lpMalloc) != NOERROR)
		return strResult; // failed to get allocator

	TCHAR szDisplayName[_MAX_PATH];
	TCHAR szBuffer[_MAX_PATH];
	BROWSEINFO browseInfo;
	browseInfo.hwndOwner = hWnd;
	browseInfo.pidlRoot = NULL; // set root at Desktop
	browseInfo.pszDisplayName = szDisplayName;
	browseInfo.lpszTitle = lpszTitle;   // passed in
	browseInfo.ulFlags = nFlags;   // also passed in
	browseInfo.lpfn = BrowseCallbackProc;      // callback func
	browseInfo.lParam = 0;      // not used
	LPITEMIDLIST lpItemIDList;

	if ((lpItemIDList = ::SHBrowseForFolder(&browseInfo)) != NULL)
	{
		// Get the path of the selected folder from the item ID list.
		if (::SHGetPathFromIDList(lpItemIDList, szBuffer))
		{
			// At this point, szBuffer contains the path the user chose.
			if (szBuffer[0] == '\0')
			{
				// SHGetPathFromIDList failed, or
				// SHBrowseForFolder failed.
				AfxMessageBox(IDS_FAILED_GET_DIRECTORY, MB_ICONSTOP|MB_OK);
			}
			else
			{
				// We have a path in szBuffer - Prepare to return it.
				strResult = szBuffer;
			}
		}
		else
		{
			// The thing referred to by lpItemIDList 
			// might not have been a file system object.
			// For whatever reason, SHGetPathFromIDList
			// didn't work!
			AfxMessageBox(IDS_FAILED_GET_DIRECTORY, MB_ICONSTOP|MB_OK);
		}
	}
	// cleanup
	lpMalloc->Free(lpItemIDList);
	lpMalloc->Release();      
	return strResult;
}

INT CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) 
{
	switch(uMsg) 
	{
	case BFFM_INITIALIZED:
		// WParam is TRUE since we are passing a path.
		// It would be FALSE if we were passing a pidl.
		SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)startingfolder.GetBuffer());
		break;
	}
	return 0;
}

CString DemanglePath(LPCTSTR path)
{
	// expand short filenames to long filenames
	StrBuf demangled;
	NtDemanglePath(const_cast<char*>((const char *)CharFromCString(path)), &demangled);
	return CharToCString(demangled.Text());
}

CString FormatError(Error *e, int flags)
{
	StrBuf buf;
	e->Fmt( &buf, flags ) ;

	return CharToCString(buf.Text());
}

int FindNoCase(CString str, CString substr, int offset/*=0*/)
{
	str.MakeLower();
	substr.MakeLower();
	return str.Find(substr, offset);
}
# Change User Description Committed
#1 19924 YourUncleBob Populate -o //guest/perforce_software/p4win/...
//guest/YourUncleBob/p4win/.....
//guest/perforce_software/p4win/main/gui/P4win.cpp
#1 16169 perforce_software Move files to follow new path scheme for branches.
//guest/perforce_software/p4win/gui/P4win.cpp
#1 8562 Matt Attaway These feet never stop running.

Initial commit of the P4Win source code.  To the best of our knowledge this
compiles and runs using the 2013.3 P4 API and VS 2010. Expect a few changes
as we refine the build process. Please post any build issues to the forums.