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

// SpecDescDlg.cpp : implementation file
//

#include "stdafx.h"
// don't really need mfc 7 for this.  just need platform sdk installed
// but this will prevent breaking the build for right now
#if _MFC_VER >= 0x0700
#define USE_THEMES
#include <uxtheme.h>
#endif
#include "p4win.h"
#include "WinPos.h"
#include "SpecDescDlg.h"
#include "MainFrm.h"
#include "FileInfoDlg.h"
#include "Historydlg.h"
#include "ViewerDlg.h"
#include "P4Fix.h"

#include "cmd_diff2.h"
#include "cmd_get.h"
#include "cmd_fixes.h"
#include "cmd_fstat.h"
#include "cmd_history.h"
#include "cmd_opened.h"
#include "cmd_prepbrowse.h"

#include "hlp\p4win.hh"

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

#define HOLD_LOCK_IF_HAVE_KEY (m_Key ? HOLD_LOCK : LOSE_LOCK)
#define UPDATE_STATUS(x) ((CMainFrame *)AfxGetMainWnd())->UpdateStatus(x)
#define	ID_EMAIL ID_EMAIL_PERFORCE
#define ID_URL   ID_WWW_PERFORCE_COM

static bool sbHasWingDings = false;

/////////////////////////////////////////////////////////////////////////////
// a subclass of CButton to pass Ctrl+F, F3 and Shift F3 to the parent window
BEGIN_MESSAGE_MAP(CKeyDownButton, CButton)
	//{{AFX_MSG_MAP(CMainFrame)
	//}}AFX_MSG_MAP
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

CKeyDownButton::CKeyDownButton(CWnd* pParent) 
	: CButton()
{
}

CKeyDownButton::~CKeyDownButton()
{
}

void CKeyDownButton::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (nChar == 'F' && GetKeyState(VK_CONTROL) & 0x8000)	// ^F == Find
		GetParent()->PostMessage(WM_COMMAND, ID_POSITIONTOPATTERN, 0);
	else if (nChar == VK_F3)
		GetParent()->PostMessage(WM_COMMAND, GetKeyState(VK_SHIFT) & 0x8000 
											 ? ID_FINDPREV : ID_FINDNEXT, 0);
	else
		CButton::OnChar(nChar, nRepCnt, nFlags);
}

/////////////////////////////////////////////////////////////////////////////
// a subclass of CButton to draw arrows to the left of the text
BEGIN_MESSAGE_MAP(CArrowButton, CButton)
	//{{AFX_MSG_MAP(CMainFrame)
	//}}AFX_MSG_MAP
	ON_WM_KEYDOWN()
	ON_WM_MOUSEMOVE()
	ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover )
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave )
	ON_WM_GETDLGCODE()
	ON_MESSAGE(BM_SETSTYLE, OnSetStyle)
END_MESSAGE_MAP()

CArrowButton::CArrowButton() 
	: CButton()
	, m_bUp(false)
	, m_bOverControl(false)
	, m_bTracking(false)
	, m_bDefault(false)
{
	m_il.Create(IDB_UPDOWNARROWS, 16, 1, RGB(0xff, 0xff, 0xff));
	m_themeLib = LoadLibrary(_T("UxTheme.dll"));
}

CArrowButton::~CArrowButton()
{
	if(m_themeLib)
		FreeLibrary(m_themeLib);
}

void CArrowButton::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (nChar == 'F' && GetKeyState(VK_CONTROL) & 0x8000)	// ^F == Find
		GetParent()->PostMessage(WM_COMMAND, ID_POSITIONTOPATTERN, 0);
	else if (nChar == VK_F3)
		GetParent()->PostMessage(WM_COMMAND, GetKeyState(VK_SHIFT) & 0x8000 
											 ? ID_FINDPREV : ID_FINDNEXT, 0);
	else
		CButton::OnChar(nChar, nRepCnt, nFlags);
}

void CArrowButton::OnMouseMove(UINT nFlags, CPoint point)
{
	if (!m_bTracking)
	{
		TRACKMOUSEEVENT tme;
		tme.cbSize = sizeof(tme);
		tme.hwndTrack = m_hWnd;
		tme.dwFlags = TME_LEAVE|TME_HOVER;
		tme.dwHoverTime = 1;
		m_bTracking = _TrackMouseEvent(&tme) != FALSE;		
	}	
	CButton::OnMouseMove(nFlags, point);
}
LRESULT CArrowButton::OnMouseHover(WPARAM wparam, LPARAM lparam)
{
	m_bOverControl=TRUE;
	Invalidate();
	return 1;
}

LRESULT CArrowButton::OnMouseLeave(WPARAM wparam, LPARAM lparam)
{
	m_bTracking = FALSE;
	m_bOverControl = FALSE;
	Invalidate(FALSE);
	return 0;
}
UINT CArrowButton::OnGetDlgCode() 
{
	UINT nCode = CButton::OnGetDlgCode();
	nCode |= (m_bDefault ? DLGC_DEFPUSHBUTTON : DLGC_UNDEFPUSHBUTTON);
	return nCode;
}

// mask for control's type
#undef  BS_TYPEMASK
#define BS_TYPEMASK SS_TYPEMASK

LRESULT CArrowButton::OnSetStyle(WPARAM wParam, LPARAM lParam)
{
	m_bDefault = wParam & BS_DEFPUSHBUTTON;
	// can't change control type after owner-draw is set.
	// let the system process changes to other style bits
	// and redrawing, while keeping owner-draw style
	return DefWindowProc(BM_SETSTYLE,
		(wParam & ~BS_TYPEMASK) | BS_OWNERDRAW, lParam);
}

#ifdef USE_THEMES
typedef HTHEME(__stdcall *PFNOPENTHEMEDATA)(HWND hwnd, LPCWSTR pszClassList);
typedef HRESULT(__stdcall *PFNCLOSETHEMEDATA)(HTHEME hTheme);
typedef HRESULT(__stdcall *PFNDRAWTHEMEBACKGROUND)(HTHEME hTheme, HDC hdc, 
	int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect);
typedef HRESULT(__stdcall *PFNGETTHEMEBACKGROUNDCONTENTRECT)(HTHEME hTheme, HDC hdc, 
	int iPartId, int iStateId, const RECT *pRect, RECT *pContentRect);
#endif

void CArrowButton::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
#ifdef USE_THEMES
	HTHEME hTheme = 0;
	if(m_themeLib)
	{
		PFNOPENTHEMEDATA pOpenThemeData = (PFNOPENTHEMEDATA)
			GetProcAddress(m_themeLib, "OpenThemeData");
		if(pOpenThemeData)
			hTheme = (pOpenThemeData)(m_hWnd, L"Button");
	}
	if(hTheme)
	{
		// attempt to map owner draw states to theme pushbutton states
		int iState = PBS_NORMAL;
		if(m_bOverControl)//lpDrawItemStruct->itemState & ODS_HOTLIGHT)
			iState = PBS_HOT;
		else if(m_bDefault)//lpDrawItemStruct->itemState & ODS_DEFAULT)
			iState = PBS_DEFAULTED;
		else if(lpDrawItemStruct->itemState & ODS_SELECTED)
			iState = PBS_PRESSED;
		HRESULT hr;

		HDC hDC = lpDrawItemStruct->hDC;
		RECT rc = lpDrawItemStruct->rcItem;

		// let theme draw background
		PFNDRAWTHEMEBACKGROUND pDrawThemeBackground = (PFNDRAWTHEMEBACKGROUND)
			GetProcAddress(m_themeLib, "DrawThemeBackground");
		if(!pDrawThemeBackground)
			return;
		hr = (pDrawThemeBackground)(hTheme, hDC, 
			BP_PUSHBUTTON, iState, &rc, 0);
		if(hr != S_OK)
			return;

		// get rect to put content in
		RECT rcContent;
		PFNGETTHEMEBACKGROUNDCONTENTRECT pGetThemeBackgroundContentRect = 
			(PFNGETTHEMEBACKGROUNDCONTENTRECT)
			GetProcAddress(m_themeLib, "GetThemeBackgroundContentRect");
		if(!pGetThemeBackgroundContentRect)
			return;
		hr = (pGetThemeBackgroundContentRect)(hTheme, hDC,
			BP_PUSHBUTTON, iState, &rc, &rcContent);
		if(hr != S_OK)
			return;

		// draw arrow as content
		CDC dc;
		dc.Attach(hDC);
		CPoint pos;
		pos.x = (rcContent.left + rcContent.right)/2 - 8;
		pos.y = (rcContent.top + rcContent.bottom)/2 - 8;
		m_il.Draw(&dc, m_bUp ? 0 : 1, pos, ILD_NORMAL);

		if (lpDrawItemStruct->itemState & ODS_FOCUS)
		{
			::DrawFocusRect(lpDrawItemStruct->hDC, &rcContent);
		}
		PFNCLOSETHEMEDATA pCloseThemeData = (PFNCLOSETHEMEDATA)GetProcAddress(m_themeLib, "CloseThemeData");
		if(!pCloseThemeData)
			return;
		(pCloseThemeData)(hTheme);
		return;
	}
	else
#endif
	{
		// if the button is defaulted, draw a black frame first
		// and then shring the rect by 1 pixel all around
		CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
		if (m_bDefault)//lpDrawItemStruct->itemState & ODS_DEFAULT)
		{
			CRect r(lpDrawItemStruct->rcItem);
			CPen *pOldPen = (CPen*)pDC->SelectStockObject(BLACK_PEN);
			CBrush *pOldBrush= (CBrush*)pDC->SelectStockObject(NULL_BRUSH);
			pDC->Rectangle(&lpDrawItemStruct->rcItem);
			pDC->SelectObject( pOldPen );
			pDC->SelectObject( pOldBrush );
			r.DeflateRect(1,1);
			lpDrawItemStruct->rcItem = r;
		}

		// This code only works with buttons.
		ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

		// If drawing selected, add the pushed style to DrawFrameControl.
		UINT uStyle = DFCS_BUTTONPUSH;
		if (lpDrawItemStruct->itemState & ODS_SELECTED)
			uStyle |= DFCS_PUSHED;

		// Make DrawFrameControl give back the content rect
		uStyle |= DFCS_ADJUSTRECT;

		// Draw the button frame.
		::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);

		// Draw the button's bitmap
		CDC dc;
		dc.Attach(lpDrawItemStruct->hDC);
		CPoint pos;
		pos.x = (lpDrawItemStruct->rcItem.right + lpDrawItemStruct->rcItem.left) / 2 - 8;
		pos.y = (lpDrawItemStruct->rcItem.bottom + lpDrawItemStruct->rcItem.top) / 2 - 8;
		m_il.Draw(&dc, m_bUp ? 0 : 1, pos, ILD_NORMAL);

		// draw a focus rect if we have focus
		if (lpDrawItemStruct->itemState & ODS_FOCUS)
		{
			CRect focusRect = lpDrawItemStruct->rcItem;
			focusRect.DeflateRect(1, 1);
			::DrawFocusRect(lpDrawItemStruct->hDC, focusRect);
		}
	}

}

/////////////////////////////////////////////////////////////////////////////
// CSpecDescDlg dialog


CSpecDescDlg::CSpecDescDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CSpecDescDlg::IDD, pParent)
    , m_SkipLines(0)
    , m_ScrollPastComments(false)
    , m_numHotSpots(0)
    , m_DescriptionW(0)
{
	//{{AFX_DATA_INIT(CSpecDescDlg)
	//}}AFX_DATA_INIT
	MainFrame()->WaitAWhileToPoll( );
	m_pParent = pParent;
	m_Caption=LoadStringResource(IDS_PERFORCE_SPECIFICATION);
	m_ReportedByTitle = "";
	m_Grey= RGB(200,200,200);
	m_GreyBrush.CreateSolidBrush( m_Grey );
	m_WinPos.SetWindow( this, _T("BrowseDlg") );
	m_Modeless     = FALSE;
	m_ShowNextPrev = FALSE;
	m_ShowShowDiffs= FALSE;
	m_ShowShowFixes= FALSE;
	m_ShowShowFiles= FALSE;
	m_ShowEditBtn  = FALSE;
	m_TurnOnReDraw = FALSE;
	m_DoNotActivate= FALSE;
	m_HasBeenMinimized= FALSE;
	m_LButtonDownTime = 0;
	m_MoreThan256Colors = FALSE;
	m_viewType = m_DiffFlag = 0;
	m_pToolTip = NULL;
	// Are we running as a Rev Hist dialog only?
	m_RevHistEnable = (TheApp()->m_RevHistPath.IsEmpty()) ? TRUE : FALSE;
	CString temp = LoadStringResource(IDS_RENAME_TO);
#ifndef UNICODE
    int numChars = MultiByteToWideChar(CP_ACP, 0, temp, -1, 0, 0);
    m_TO_ = new WCHAR[numChars];
    MultiByteToWideChar(CP_ACP, 0, temp, -1, const_cast<LPWCH>(m_TO_), numChars);
#else
    m_TO_ = new WCHAR[temp.GetLength() + 1];
	lstrcpy(const_cast<WCHAR*>(m_TO_), temp);
#endif
	m_NextBtn.m_bUp = false;
	m_PrevBtn.m_bUp = true;
	m_InitRect.SetRect(0,0,0,0);
	m_ChkServerBusy = !SERVER_BUSY();	// if busy at init, it'll always be busy
	m_Key = 0;
	m_pFRDlg = NULL;
	m_FindWhatStr = MainFrame()->GetFindWhatStr();
	m_FindWhatFlags = (MainFrame()->GetFindWhatFlags() | FR_DOWN) & ~FR_HIDEWHOLEWORD;
}

CSpecDescDlg::~CSpecDescDlg()
{
	// can't use MainFrame()-> construct
	// because mainfram might have closed.
	CMainFrame * mainWnd = MainFrame();
	if (mainWnd)
	{
		mainWnd->SetGotUserInput( );
		mainWnd->WaitAWhileToPoll( );
	}
#ifndef UNICODE
    // for UNICODE build, m_DescriptionW just points at m_Description
    delete const_cast<LPWCH>(m_DescriptionW);
#endif
    delete const_cast<LPWCH>(m_TO_);
	if (m_pToolTip)
		delete m_pToolTip;
}

void CSpecDescDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CSpecDescDlg)
	DDX_Control(pDX, IDC_DESCRIPTION, m_Text);
	DDX_Control(pDX, IDC_PREVITEM, m_PrevBtn);
	DDX_Control(pDX, IDC_NEXTITEM, m_NextBtn);
	DDX_Control(pDX, IDC_SHOWDIFFS, m_btShowDiffs);
	DDX_Control(pDX, IDOK, m_CloseBtn);
	DDX_Control(pDX, IDC_PRINT, m_PrintBtn);
	DDX_Control(pDX, IDC_EDITIT, m_EditBtn);
	DDX_Control(pDX, IDC_SHOWFIXES, m_ShowFixesBtn);
	DDX_Control(pDX, IDC_SHOWFILES, m_ShowFilesBtn);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CSpecDescDlg, CDialog)
	ON_NOTIFY(EN_MSGFILTER, IDC_DESCRIPTION, OnMsgfilterDescription)
	//{{AFX_MSG_MAP(CSpecDescDlg)
	ON_WM_SIZE()
	ON_WM_CLOSE()
	ON_WM_DESTROY()
	ON_WM_SHOWWINDOW()
	ON_WM_GETMINMAXINFO()
	ON_BN_CLICKED(IDC_NEXTITEM, OnNextitem)
	ON_BN_CLICKED(IDC_PREVITEM, OnPrevitem)
	ON_BN_CLICKED(IDC_SHOWDIFFS, OnShowDiffsNormal)
	ON_BN_CLICKED(IDC_SHOWDIFFSMENU, OnShowDiffsBtn)
	ON_COMMAND(ID_SHOWDIFFS_NORMAL, OnShowDiffsNormal)
	ON_COMMAND(ID_SHOWDIFFS_SUMMARY, OnShowDiffsSummary)
	ON_COMMAND(ID_SHOWDIFFS_UNIFIED, OnShowDiffsUnified)
	ON_COMMAND(ID_SHOWDIFFS_CONTEXT, OnShowDiffsContext)
	ON_COMMAND(ID_SHOWDIFFS_RCS, OnShowDiffsRCS)
	ON_COMMAND(ID_SHOWDIFFS_NONE, OnShowDiffsNone)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_NORMAL, OnUpdateShowDiffsNormal)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_SUMMARY, OnUpdateShowDiffsSummary)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_UNIFIED, OnUpdateShowDiffsUnified)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_CONTEXT, OnUpdateShowDiffsContext)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_RCS, OnUpdateShowDiffsRCS)
	ON_UPDATE_COMMAND_UI(ID_SHOWDIFFS_NONE, OnUpdateShowDiffsNone)
	ON_BN_CLICKED(IDC_PRINT, OnPrint)
	ON_WM_SYSCOMMAND()
	ON_COMMAND(ID_FILE_GETCUSTOM, OnSync)
	ON_UPDATE_COMMAND_UI(ID_FILE_GETCUSTOM, OnUpdateSync)
	ON_COMMAND(ID_POSITIONDEPOT, OnPositionDepot)
	ON_UPDATE_COMMAND_UI(ID_POSITIONDEPOT, OnUpdatePositionDepot)
	ON_COMMAND(ID_FILE_DIFFHEAD, OnDiffHead)
	ON_UPDATE_COMMAND_UI(ID_FILE_DIFFHEAD, OnUpdateDiffHead)
	ON_COMMAND(IDB_DIFFREVISIONS, OnDiffPrev)
	ON_UPDATE_COMMAND_UI(IDB_DIFFREVISIONS, OnUpdateDiffPrev)
	ON_COMMAND(IDB_DIFFCLIFILE, OnDiffCliFile)
	ON_UPDATE_COMMAND_UI(IDB_DIFFCLIFILE, OnUpdateDiffCliFile)
	ON_COMMAND(IDB_BROWSE, OnFileAutobrowse)
	ON_UPDATE_COMMAND_UI(IDB_BROWSE, OnUpdateFileAutobrowse)
	ON_COMMAND(ID_FILE_ANNOTATE, OnFileAnnotate)
	ON_UPDATE_COMMAND_UI(ID_FILE_ANNOTATE, OnUpdateFileAnnotate)
	ON_COMMAND(ID_FILE_PROPERTIES, OnFileInformation)
	ON_UPDATE_COMMAND_UI(ID_FILE_PROPERTIES, OnUpdateFileInformation)
	ON_COMMAND(ID_FILE_REVISIONHISTORY, OnFileRevisionhistory)
	ON_UPDATE_COMMAND_UI(ID_FILE_REVISIONHISTORY, OnUpdateFileRevisionhistory)
	ON_COMMAND(ID_FILE_REVISIONTREE, OnFileRevisionTree)
	ON_UPDATE_COMMAND_UI(ID_FILE_REVISIONTREE, OnUpdateFileInformation)
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
	ON_COMMAND(ID_PERFORCE_OPTIONS, OnOptions)
    ON_UPDATE_COMMAND_UI(ID_PERFORCE_OPTIONS, OnUpdateOptions)
	ON_COMMAND(ID_CHANGE_DESCRIBE, OnDescChg)
	ON_UPDATE_COMMAND_UI(ID_CHANGE_DESCRIBE, OnUpdateDescChg)
	ON_COMMAND(ID_BRANCH_DESCRIBE, OnDescBranch)
	ON_UPDATE_COMMAND_UI(ID_BRANCH_DESCRIBE, OnUpdateDescBranch)
	ON_COMMAND(ID_LABEL_DESCRIBE, OnDescLabel)
	ON_UPDATE_COMMAND_UI(ID_LABEL_DESCRIBE, OnUpdateDescLabel)
	ON_COMMAND(ID_CLIENT_DESCRIBE, OnDescClient)
	ON_UPDATE_COMMAND_UI(ID_CLIENT_DESCRIBE, OnUpdateDescClient)
	ON_COMMAND(ID_USER_DESCRIBE, OnDescUser)
	ON_UPDATE_COMMAND_UI(ID_USER_DESCRIBE, OnUpdateDescUser)
	ON_COMMAND(ID_JOB_DESCRIBE, OnDescJob)
	ON_UPDATE_COMMAND_UI(ID_JOB_DESCRIBE, OnUpdateDescJob)
	ON_COMMAND(ID_EMAIL, OnEmail)
	ON_UPDATE_COMMAND_UI(ID_EMAIL, OnUpdateEmail)
	ON_COMMAND(ID_URL, OnURL)
	ON_UPDATE_COMMAND_UI(ID_URL, OnUpdateURL)
	ON_COMMAND(ID_DIFF2, OnDiff2)
	ON_UPDATE_COMMAND_UI(ID_DIFF2, OnUpdateDiff2)
	ON_COMMAND(ID_CURRENTTASK, OnCallTrack)
	ON_UPDATE_COMMAND_UI(ID_CURRENTTASK, OnUpdateCallTrack)
	ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
	ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateEditSelectAll)
	ON_UPDATE_COMMAND_UI(ID_POSITIONTOPATTERN, OnUpdatePositionToPattern)
	ON_COMMAND(ID_POSITIONTOPATTERN, OnPositionToPattern)
	ON_UPDATE_COMMAND_UI(ID_FINDNEXT, OnUpdatePositionToNext)
	ON_COMMAND(ID_FINDNEXT, OnPositionToNext)
	ON_UPDATE_COMMAND_UI(ID_FINDPREV, OnUpdatePositionToPrev)
	ON_COMMAND(ID_FINDPREV, OnPositionToPrev)
	ON_BN_CLICKED(IDC_SHOWFIXES, OnShowfixes)
	ON_BN_CLICKED(IDC_SHOWFILES, OnShowfiles)
	ON_WM_HELPINFO()
	ON_COMMAND(ID_HELPNOTES, OnQuickHelp)
	ON_COMMAND(ID_HELP, OnHelp)
	ON_COMMAND(IDCANCEL, OnClose)
	ON_BN_CLICKED(IDC_EDITIT, OnEditButton)
	ON_WM_CONTEXTMENU()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_P4DIFF2, OnP4Diff2 )
	ON_MESSAGE(WM_P4PREPBROWSE, OnP4ViewFile )
	ON_MESSAGE(WM_P4FIXES, OnP4Fixes )
	ON_MESSAGE(WM_P4DESCRIBE, OnP4Describe )
	ON_MESSAGE(WM_P4FILEINFORMATION, OnP4FileInformation )
	ON_MESSAGE(WM_P4ENDFILEINFORMATION, OnP4EndFileInformation )
	ON_MESSAGE(WM_P4FSTAT, OnP4LabelContents )
    ON_MESSAGE(WM_P4ENDDESCRIBE, OnP4EndDescribe )
	ON_MESSAGE(WM_NEWCLIENT, OnNewClient )
	ON_MESSAGE(WM_NEWUSER, OnNewUser )
	ON_MESSAGE(WM_QUITTING, OnQuitting )
	ON_MESSAGE(WM_FINDPATTERN, OnFindPattern )
    ON_WM_INITMENUPOPUP()
    ON_REGISTERED_MESSAGE( WM_FINDREPLACE, OnFindReplace )
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSpecDescDlg message handlers

BOOL CSpecDescDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();

	if (m_Modeless)
	{
		DWORD dwStyle = GetWindowLong(m_hWnd, GWL_STYLE);
		SetWindowLong(m_hWnd, GWL_STYLE, dwStyle | WS_MINIMIZEBOX);
		MainFrame()->SetModelessWnd(this);
	}

	m_bDiffOutput = m_Caption.Find(_T(" <> ")) > 0;

	GetWindowRect(&m_InitRect);
	if (m_ShowNextPrev)
	{
		GetDlgItem(IDC_PREVITEM)->ShowWindow(SW_SHOWNORMAL);
		GetDlgItem(IDC_NEXTITEM)->ShowWindow(SW_SHOWNORMAL);
	}

	if (m_ShowEditBtn)
		GetDlgItem(IDC_EDITIT)->ShowWindow(SW_SHOWNORMAL);

	if (m_ShowShowDiffs && !m_Key)
	{
		m_btShowDiffs.ShowWindow(SW_SHOWNORMAL);
		m_btShowDiffs.EnableWindow(TRUE);

		// Set the 2 IDs for the Diff button
		m_btShowDiffs.SetIDs(IDC_SHOWDIFFS, IDC_SHOWDIFFSMENU);
		m_btShowDiffs.SetSplit(m_DiffFlag != ID_SHOWDIFFS_NORMAL);

		//Set up the tooltip
		m_pToolTip = new CToolTipCtrl;
		if (!m_pToolTip->Create(this))
		{
		   TRACE("Unable To create ToolTip\n");
		}
		else if (!m_pToolTip->AddTool(&m_btShowDiffs, LoadStringResource(IDS_SHOWDIFFS_BTN_TOOLTIP)))
		{
		   TRACE("Unable to add Show Diffs button to the tooltip\n");
		}
		m_pToolTip->Activate(TRUE);
	}
	else
	{
		GetMenu()->DeleteMenu(3, MF_BYPOSITION);
	}

	if (m_ShowShowFixes)
	{
		GetDlgItem(IDC_SHOWFIXES)->ShowWindow(SW_SHOWNORMAL);
		GetDlgItem(IDC_SHOWFIXES)->EnableWindow(TRUE);
	}

	if (m_ShowShowFiles)
	{
		GetDlgItem(IDC_SHOWFILES)->ShowWindow(SW_SHOWNORMAL);
		GetDlgItem(IDC_SHOWFILES)->EnableWindow(TRUE);
	}

	if (!MainFrame()->HaveP4QTree())
		GetMenu()->DeleteMenu(ID_FILE_REVISIONTREE, MF_BYCOMMAND);

	// Set the font to the fixed dialog font
	CreateTheFont();

	// And then initialize the window position and edit control
	m_WinPos.RestoreWindowPosition();

    SetEditText();
    m_Text.SetEventMask(ENM_KEYEVENTS|ENM_LINK);
	m_Text.SetBackgroundColor(FALSE, GetSysColor(COLOR_BTNFACE));	// set background color to gray

    CHARFORMAT cf;
    cf.cbSize = sizeof(cf);
    cf.dwMask = CFM_COLOR;
    cf.crTextColor = GetSysColor(COLOR_BTNTEXT);

	m_Text.SetSel(0, m_Description.GetLength());
	m_Text.SetSelectionCharFormat(cf);
	m_Text.SetSel(0, 0);

	if( m_ScrollPastComments )
		ScrollPastComments();

	SetHotSpots();

	SetWindowText(m_Caption);

	CMenu* hSysMenu = GetSystemMenu( FALSE );
	UINT uWhere = hSysMenu->GetMenuItemCount() - 2;
	// insert new items in reverse order
	hSysMenu->InsertMenu( uWhere, MF_STRING | MF_BYPOSITION, ID_HELP, LoadStringResource(IDS_SPECDESC_HELP) );
	hSysMenu->InsertMenu( uWhere, MF_STRING | MF_BYPOSITION, IDC_COMMAND, LoadStringResource(IDS_SPECDESC_MORECOMMANDS) );
	hSysMenu->InsertMenu( uWhere, MF_SEPARATOR | MF_BYPOSITION );
	hSysMenu->InsertMenu( uWhere, MF_STRING | MF_BYPOSITION, IDC_PRINT, LoadStringResource(IDS_SPECDESC_PRINT) );	
	hSysMenu->InsertMenu( uWhere, MF_STRING | MF_BYPOSITION, ID_PAGE_SETUP, LoadStringResource(IDS_SPECDESC_PAGESETUP) );
	hSysMenu->InsertMenu( uWhere, MF_SEPARATOR | MF_BYPOSITION );

	// remove the calltrack menu item if the super-secret reg setting isn't present:
	if (!AfxGetApp()->GetProfileInt(_T("Settings"), _T("CallTrack"), 0))
		GetMenu()->DeleteMenu(ID_CURRENTTASK,MF_BYCOMMAND);

	m_TurnOnReDraw = TRUE;

	// Always turn repainting back on - it might still be off and we currently can't
	// reliably detect those cases when we don't have to turn it back on.
	EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE);

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

void CSpecDescDlg::OnShowWindow(BOOL bShow, UINT nStatus) 
{
	CDialog::OnShowWindow(bShow, nStatus);

	if (bShow)
		SetWindowPos(&wndTop, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
}
	
void CSpecDescDlg::CreateTheFont()
{
	CString face= GET_P4REGPTR()->GetFontFace();
	int size= GET_P4REGPTR()->GetFontSize();
	int weight= GET_P4REGPTR()->GetFontWeight();
	BOOL isItalic= GET_P4REGPTR()->GetFontItalic();

	LOGFONT logFont;
	CWindowDC dc(this);
	int i = GetDeviceCaps(dc.m_hDC, NUMCOLORS);
	m_MoreThan256Colors = ((i < 0) || (i > 256)) ? TRUE : FALSE;
	memset( &logFont, 0, sizeof(LOGFONT) );

	// create the regular dialog font
	logFont.lfCharSet = DEFAULT_CHARSET;
	logFont.lfPitchAndFamily= FIXED_PITCH | FF_DONTCARE;
	lstrcpy(logFont.lfFaceName, face.GetBuffer(face.GetLength()));
	logFont.lfHeight= -abs(size);	 
	logFont.lfWeight = weight;			//Regular	
	logFont.lfItalic = (BYTE) isItalic;
	logFont.lfCharSet = DEFAULT_CHARSET;

	m_Font.CreateFontIndirect( &logFont );
	m_Text.SetFont(&m_Font);
}

void CSpecDescDlg::SetEditText()
{
#ifndef UNICODE
    int numChars = MultiByteToWideChar(CP_ACP, 0, m_Description, -1, 0, 0);
    delete const_cast<LPWCH>(m_DescriptionW);
    m_DescriptionW = new WCHAR[numChars];
    MultiByteToWideChar(CP_ACP, 0, m_Description, -1, const_cast<LPWCH>(m_DescriptionW), numChars);
#else
    m_DescriptionW = m_Description;
#endif
	m_Text.SetWindowText(m_Description);
}

void CSpecDescDlg::ScrollPastComments()
{
    if(m_ScrollPastComments)
    {
        if(m_SkipLines == 0)
        {
            CString line;

            // Most specification output begins with a huge block of comment text
            // scroll past all that rot
            for( int i=0; i < m_Text.GetLineCount(); i++ )
            {
                const int MAX_LINE_LEN = 1024;
                LPTSTR buf= line.GetBuffer( MAX_LINE_LEN + 1);
                int size = m_Text.GetLine( i, buf, MAX_LINE_LEN );
                buf[size] = 0;
                line.ReleaseBuffer();
                line.TrimLeft(_T(" \t"));
                if( line.GetLength() > 0 && line[0] == _T('#') )
                    m_SkipLines++;
            }
        }
        m_Text.LineScroll(m_SkipLines);
        m_Text.SetSel(m_Text.LineIndex(m_SkipLines), m_Text.LineIndex(m_SkipLines));
    }
}

void CSpecDescDlg::SetDescription(LPCTSTR txt, BOOL scrollPastComments /*=TRUE*/)
{
    // copy to m_Description, replacing "\r\n" with "\n"
    // equivalent to but much, much faster than: 
    // m_Description = txt;
    // m_Description.Replace(_T("\r\n"), _T("\n"));
	LPTSTR pDesc = m_Description.GetBufferSetLength(lstrlen(txt)+1);
    while(*txt)
    {
        if(*txt == _T('\r') && txt[1] == _T('\n'))
        {
            txt++;
            *pDesc++ = *txt++;
        }
        else
        {
#ifdef UNICODE
			*pDesc++ = *txt++;
#else
            _tccpy(pDesc, txt);
            pDesc = CharNext(pDesc);
            txt = CharNext(txt);
#endif
        }
    }
    *pDesc = 0;
    m_Description.ReleaseBuffer();

	m_ScrollPastComments= scrollPastComments;
    m_SkipLines = 0;

	int lgth = m_Description.GetLength();
    if(lgth > 32000)
    {
	    // 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);

		int trunc = 0;
    	if(brittleWare)
			trunc = 32000;
		else if (lgth > 60020)
		{
			if (osVer.dwMajorVersion < 5)
				trunc = 60000;
			else if (lgth > 256000)
				trunc = 256000;
		}
		if (trunc)
		{
			CString txt;
			txt.FormatMessage(IDS_DESCRIPTION_TRUNCATED_n, trunc);
	    	m_Description = m_Description.Left(trunc) + txt;
		}
    }
}

void CSpecDescDlg::OnSize(UINT nType, int cx, int cy) 
{
	CRect rect;

	CDialog::OnSize(nType, cx, cy);

	if (nType == SIZE_MINIMIZED)
	{
		if (!m_Item.IsEmpty())
			SetWindowText(m_Item);
	}
	else if (m_HasBeenMinimized)
		SetWindowText(m_Caption);

	GetClientRect(&rect);
	int x=rect.Width();
	int y=rect.Height();
	
	// Slide the OK button to the right of dlg
	CWnd *pOK=GetDlgItem(IDOK);
	if(pOK != NULL && IsWindow(pOK->m_hWnd))
	{
		pOK->GetWindowRect(&rect);
		ScreenToClient(&rect);
		pOK->MoveWindow(x-rect.Width()-4, y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
		pOK->RedrawWindow();
		CWnd *pBtn = GetDlgItem(IDC_PRINT);
		pBtn->MoveWindow(4, y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
		pBtn->RedrawWindow();
		if (m_ShowNextPrev)
		{
			pBtn = GetDlgItem(IDC_PREVITEM);
			pBtn->MoveWindow(4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
			pBtn->RedrawWindow();
			pBtn = GetDlgItem(IDC_NEXTITEM);
			pBtn->MoveWindow(4+rect.Width()+4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
			pBtn->RedrawWindow();
		}
		if (m_ShowEditBtn)
		{
			pBtn = GetDlgItem(IDC_EDITIT);
			pBtn->MoveWindow(4+rect.Width()+4+rect.Width()+4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
			pBtn->RedrawWindow();
		}
		if (m_ShowShowDiffs)
		{
			pBtn = GetDlgItem(IDC_SHOWDIFFS);
			pBtn->MoveWindow(4+rect.Width()+4+rect.Width()+4+rect.Width()+4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width()+18, rect.Height(), TRUE); 
			pBtn->RedrawWindow();
		}
		if (m_ShowShowFixes)
		{
			pBtn = GetDlgItem(IDC_SHOWFIXES);
			pBtn->MoveWindow(4+rect.Width()+4+rect.Width()+4+rect.Width()+4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
			pBtn->RedrawWindow();
		}
		if (m_ShowShowFiles)
		{
			pBtn = GetDlgItem(IDC_SHOWFILES);
			pBtn->MoveWindow(4+rect.Width()+4+rect.Width()+4+rect.Width()+4+rect.Width()+4, 
				y-rect.Height()-4, rect.Width(), rect.Height(), TRUE); 
			pBtn->RedrawWindow();
		}

		// Increase the size of the edit control above the button
		m_Text.MoveWindow(4, 4, x-8, y-rect.Height()-14, TRUE);
	}
}

LRESULT CSpecDescDlg::OnQuitting(WPARAM wParam, LPARAM lParam)
{
	// P4Win is terminating, so close this dialog.
	CDialog::OnOK();
	return 0;
}

void CSpecDescDlg::OnClose() 
{
	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, 0, (LPARAM)this);
	CDialog::OnOK();
}

void CSpecDescDlg::OnOK() 
{
	// If server is busy, wait a bit before exiting
	if( m_ChkServerBusy && SERVER_BUSY() )
	{
		Sleep(0);
		SET_BUSYCURSOR();
		// wait a bit in 1/10 sec intervals to see if the server request finishes
		int t=GET_P4REGPTR()->BusyWaitTime();
		do
		{
			Sleep(50);
			t -= 50;
		} while (SERVER_BUSY() && t > 0);
		if( SERVER_BUSY() )
		{
			::PostMessage(MainFrame()->m_hWnd, WM_COMMAND, ID_CANCEL_BUTTON, 0);
			return;
		}
	}

	m_WinPos.SaveWindowPosition();

	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, 0, (LPARAM)this);
	CDialog::OnOK();
}

// These 2 routines handle the Next and Prev buttons.
//
// Note that we have to turn off the painting for all child windows of the main P4Win window
// to prevent flashing.  This works because the describe dialogbox is not a child of the
// P4win main window - it's a top level window of its own.
// We turn the painting back on in the caller of this dialogbox when they pess OK, Enter or ESC

void CSpecDescDlg::OnNextitem() 
{
	if ((!m_Key && SERVER_BUSY()) || !m_ShowNextPrev)
	{
		MessageBeep(0);
		return;
	}
	m_WinPos.SaveWindowPosition();
	SetWindowText( LoadStringResource(IDS_PERFORCE_DESCRIPTION) );
	m_Text.SetWindowText( LoadStringResource(IDS_LOADING) );
	UpdateWindow( );

	// Turn off all painting in children of main window to prevent flashing
	EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, FALSE);

	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, IDC_NEXTITEM, (LPARAM)this);
	CDialog::EndDialog(IDC_NEXTITEM);
}

void CSpecDescDlg::OnPrevitem() 
{
	if ((!m_Key && SERVER_BUSY()) || !m_ShowNextPrev)
	{
		MessageBeep(0);
		return;
	}
	m_WinPos.SaveWindowPosition();
	SetWindowText( LoadStringResource(IDS_PERFORCE_DESCRIPTION) );
	m_Text.SetWindowText( LoadStringResource(IDS_LOADING) );
	UpdateWindow( );

	// Turn off all painting in children of main window to prevent flashing	
	EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, FALSE);

	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, IDC_PREVITEM, (LPARAM)this);
	CDialog::EndDialog(IDC_PREVITEM);
}

void CSpecDescDlg::OnUpdateShowDiffsNormal(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag != ID_SHOWDIFFS_NORMAL);
}

void CSpecDescDlg::OnUpdateShowDiffsSummary(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag != ID_SHOWDIFFS_SUMMARY);
}

void CSpecDescDlg::OnUpdateShowDiffsUnified(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag != ID_SHOWDIFFS_UNIFIED);
}

void CSpecDescDlg::OnUpdateShowDiffsContext(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag != ID_SHOWDIFFS_CONTEXT);
}

void CSpecDescDlg::OnUpdateShowDiffsRCS(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag != ID_SHOWDIFFS_RCS);
}

void CSpecDescDlg::OnUpdateShowDiffsNone(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_ShowShowDiffs && m_DiffFlag);
}

void CSpecDescDlg::OnShowDiffsBtn() 
{
	CPoint point;
	CRect rect;
	GetDlgItem(IDC_SHOWDIFFS)->GetWindowRect(&rect);
	point.x = rect.left;
	point.y = rect.bottom + 1;
	OnContextMenu(GetDlgItem(IDC_SHOWDIFFS), point);
	m_btShowDiffs.ClearButtonPushed();
}

void CSpecDescDlg::OnShowDiffsNormal() 
{
	if (m_DiffFlag == ID_SHOWDIFFS_NORMAL)
		OnShowDiffsBtn();
	else
		OnShowDiffs(ID_SHOWDIFFS_NORMAL);
}

void CSpecDescDlg::OnShowDiffsSummary() 
{
	OnShowDiffs(ID_SHOWDIFFS_SUMMARY);
}

void CSpecDescDlg::OnShowDiffsUnified() 
{
	OnShowDiffs(ID_SHOWDIFFS_UNIFIED);
}

void CSpecDescDlg::OnShowDiffsContext() 
{
	OnShowDiffs(ID_SHOWDIFFS_CONTEXT);
}

void CSpecDescDlg::OnShowDiffsRCS() 
{
	OnShowDiffs(ID_SHOWDIFFS_RCS);
}

void CSpecDescDlg::OnShowDiffsNone() 
{
	OnShowDiffs(ID_SHOWDIFFS_NONE);
}

void CSpecDescDlg::OnShowDiffs(int flag) 
{
	if ((!m_Key && SERVER_BUSY()) || !m_ShowShowDiffs)
	{
		MessageBeep(0);
		return;
	}
	m_WinPos.SaveWindowPosition();
	SetWindowText( LoadStringResource(IDS_LOADING_DIFFS) );
	UpdateWindow( );

	// Turn off all painting in children of main window to prevent flashing	
	EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, FALSE);

	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, flag, (LPARAM)this);
	CDialog::EndDialog(flag);
}

void CSpecDescDlg::OnEditButton() 
{
	if (SERVER_BUSY() || !m_ShowEditBtn)
	{
		MessageBeep(0);
		return;
	}
	m_WinPos.SaveWindowPosition();
	if (m_pParent && m_Modeless)
		m_pParent->PostMessage(WM_P4ENDDESCRIBE, IDC_EDITIT, (LPARAM)this);
	CDialog::EndDialog(IDC_EDITIT);
}

void CSpecDescDlg::OnPrint() 
{
	CString outBuf = m_Description;

	// remove comments from description
	int i;
	while (outBuf.GetAt(0) == _T('#'))
	{
		if ((i = outBuf.Find(_T('\n'))) == -1)
			break;
		outBuf = outBuf.Right(outBuf.GetLength() - i - 1);
	}
	outBuf.TrimLeft(_T("\r\n"));
	outBuf.TrimRight(_T("\r\n\t "));

	// if the whole spec is comments, they must be printing the spec def itself.
	if (outBuf.IsEmpty())
		outBuf = m_Description;

	MainFrame()->PrintString(outBuf, m_Caption);
}

void CSpecDescDlg::OnPageSetup() 
{
	MainFrame()->PageSetup();
}

void CSpecDescDlg::OnSysCommand(UINT nID, LPARAM lParam) 
{
	switch(nID)
	{
	case ID_PAGE_SETUP:
		OnPageSetup();
		return;

	case IDC_PRINT:
		OnPrint();
		return;

	case ID_HELP:
		OnHelp();
		return;

	case SC_MINIMIZE:
		GetDesktopWindow()->ArrangeIconicWindows();
		break;
	}

	CDialog::OnSysCommand(nID, lParam);
}

void CSpecDescDlg::SetMenuFlags()
{
	long nStartChar;
	long nEndChar;
	m_fPopup = MF_GRAYED | MF_DISABLED | MF_POPUP;
	m_fMarked= MF_GRAYED | MF_STRING;
	m_fFile = MF_GRAYED | MF_STRING;
	m_fHist = MF_GRAYED | MF_STRING;
	m_fRev  = MF_GRAYED | MF_STRING;
	m_fProp = MF_GRAYED | MF_STRING;
	m_fChg  = MF_GRAYED | MF_STRING;
	m_fClTk = MF_GRAYED | MF_STRING;
	m_fItem = MF_GRAYED | MF_STRING;
	m_fCli  = MF_GRAYED | MF_STRING;
	m_fUser = MF_GRAYED | MF_STRING;
	m_fJob  = MF_GRAYED | MF_STRING;
	m_fEmail= MF_GRAYED | MF_STRING;
	m_fURL  = MF_GRAYED | MF_STRING;
	m_fDiff2= MF_GRAYED | MF_STRING;

	m_Text.GetSel( nStartChar,  nEndChar );
	if ( nStartChar < nEndChar )
	{
		int i = IsItaHotSpot(nStartChar, nEndChar);
		UINT fHotSpot = (i == -1) ? 0 : m_HotSpotType.GetAt(i);
		BOOL b1line = m_Text.LineFromChar(nStartChar) == m_Text.LineFromChar(nEndChar);
		m_fMarked= MF_ENABLED | MF_STRING;
		CString selText = m_Text.GetSelText();
		selText.TrimRight();
		selText.TrimLeft();
		switch(fHotSpot)
		{
		case HS_ISAFILE:
			m_fFile = MF_ENABLED | MF_STRING;
			break;
		case HS_ISACHG:
			m_fChg = MF_ENABLED | MF_STRING;
			break;
		case HS_ISAUSER:
			m_fUser = MF_ENABLED | MF_STRING;
			break;
		case HS_ISACLIENT:
			m_fCli = MF_ENABLED | MF_STRING;
			break;
		case HS_ISAJOB:
			m_fJob = MF_ENABLED | MF_STRING;
			break;
		case HS_ISAEMAIL:
			m_fEmail = MF_ENABLED | MF_STRING;
			break;
		case HS_ISAURL:
			m_fURL = MF_ENABLED | MF_STRING;
			break;
		case HS_ISDIFF2:
			m_fDiff2 = MF_ENABLED | MF_STRING;
			break;
		default:
			m_fFile = ((((selText.GetAt(0) == _T('/')) && (selText.GetAt(1) == _T('/')))
					 || ((selText.GetAt(0) == _T('"')) && (selText.GetAt(1) == _T('/')) && (selText.GetAt(2) == _T('/'))))
					&& (selText.Find(_T("//"), 2) == -1))
					? MF_ENABLED | MF_STRING : MF_GRAYED | MF_STRING;
			if (m_fFile == (MF_ENABLED | MF_STRING))
				break;
			if ((i = selText.Find(_T('@'))) != -1)
			{
				if (b1line && (selText.Find(_T(' ')) == -1))
				{
					if (selText.Find(_T('.'), i) != -1)
						m_fEmail = MF_ENABLED | MF_STRING;
					else
					{
						m_fPopup = MF_POPUP;
						m_fCli = m_fUser = MF_ENABLED | MF_STRING;
					}
				}
			}
			else if (b1line && (selText.Find(_T(' ')) == -1))
			{
				m_fPopup = MF_POPUP;
				if (_istdigit(selText.GetAt(0)))
				{
					m_fChg = m_fClTk = MF_ENABLED | MF_STRING;
					for (int i = 0; ++i < selText.GetLength(); )
					{
						if (!_istdigit(selText.GetAt(i)))
						{
							m_fChg = m_fClTk = MF_GRAYED | MF_STRING;
							break;
						}
					}
				}
				m_fItem = m_fCli = m_fUser = m_fJob = (m_fChg == (MF_GRAYED | MF_STRING)) 
						? MF_ENABLED | MF_STRING : MF_GRAYED | MF_STRING;
			}
			break;
		}
		if (m_fFile == (MF_ENABLED | MF_STRING))
		{
			m_fRev = selText.Find(_T('#')) > 2 ? MF_ENABLED | MF_STRING : MF_GRAYED | MF_STRING;
			if ((selText.Find(_T("...")) == -1) && (selText.FindOneOf(_T("*?")) == -1))
			{
				m_fProp = MF_ENABLED | MF_STRING;
				if (m_RevHistEnable)
					m_fHist = MF_ENABLED | MF_STRING;
			}
		}
	}
}

int CSpecDescDlg::IsItaHotSpot(int nStartChar, int nEndChar)
{
	int i;
	for (i = -1; ++i < m_numHotSpots; )
	{
		int	b = m_HotSpotBgn.GetAt(i);
		int	e = m_HotSpotEnd.GetAt(i);
		if ((nStartChar == b) && (nEndChar == e))
			return i;
		else if (nStartChar < b)
			break;
	}
	return -1;
}

void CSpecDescDlg::OnMsgfilterDescription(NMHDR* pNMHDR, LRESULT* pResult) 
{
	MSGFILTER *pMsgFilter = reinterpret_cast<MSGFILTER *>(pNMHDR);
	*pResult = 0;

    if(pMsgFilter->nmhdr.idFrom == IDC_DESCRIPTION)
    {
		if(pMsgFilter->msg == WM_KEYDOWN)
		{
			// trap ctrl-c keydown message
			if(GetKeyState(VK_CONTROL) & 0x8000)
			{
				if (pMsgFilter->wParam == 0x43)			// ^C
				{
					// richedit should not process this message
					*pResult = 1;   
					// translate to a copy command so we can modify the copy process
					PostMessage(WM_COMMAND, ID_EDIT_COPY, 0);
				}
				else if (pMsgFilter->wParam == 0x46)	// ^F
				{
					// richedit should not process this message
					*pResult = 1;   
					// translate to a find command
					PostMessage(WM_COMMAND, ID_POSITIONTOPATTERN, 0);
				}
				else if (pMsgFilter->wParam == 0x54)	// ^T
				{
					// richedit should not process this message
					*pResult = 1;   
					OnOptions();
				}
			}
			else if(GetKeyState(VK_SHIFT) & 0x8000)
			{
				if (pMsgFilter->wParam == VK_F3)			// Shift+F3
				{
					// richedit should not process this message
					*pResult = 1;   
					// translate to a find previous command
					PostMessage(WM_COMMAND, ID_FINDPREV, 0);
				}
			}
			else
			{
				if (pMsgFilter->wParam == VK_F3)			// F3
				{
					// richedit should not process this message
					*pResult = 1;   
					// translate to a find next command
					PostMessage(WM_COMMAND, ID_FINDNEXT, 0);
				}
			}
		}
		else if(pMsgFilter->msg == WM_KEYUP)
		{
			if (pMsgFilter->wParam == VK_TAB)			// TAB key coming up == Desc got focus
				MakeSmartSelection();
		}
    }
}

void CSpecDescDlg::MakeSmartSelection()
{
	CHARRANGE cr, crtmp;
	m_Text.GetSel(cr);
	if (!cr.cpMin)
	{
		m_Text.LineScroll(0 - m_Text.GetFirstVisibleLine() + m_SkipLines);
		int nFirstVisChar = m_Text.LineIndex(m_SkipLines);
		crtmp.cpMin = 0;
		crtmp.cpMax = -1;
		m_Text.GetSel(crtmp);
		if (cr.cpMax >= crtmp.cpMax)
		{
			CPoint pt;
			pt.x = pt.y = 0;
			cr.cpMin = cr.cpMax = nFirstVisChar;
		}
		m_Text.SetSel(cr);
	}
}

void CSpecDescDlg::OnEditCopy() 
{
    CString selText= MakeLFs(m_Text.GetSelText());
    CopyTextToClipboard(selText);
}

void CSpecDescDlg::OnUpdateEditSelectAll(CCmdUI* pCmdUI) 
{
    // this should always be enabled
	pCmdUI->Enable(TRUE);
}

void CSpecDescDlg::OnEditSelectAll() 
{
	m_Text.SetSel( 0, -1 );
}

void CSpecDescDlg::OnUpdateOptions(CCmdUI* pCmdUI) 
{
    // this should always be enabled
	pCmdUI->Enable(TRUE);
}

void CSpecDescDlg::OnOptions() 
{
	CString face = GET_P4REGPTR()->GetFontFace();
	int size     = GET_P4REGPTR()->GetFontSize();
	int weight   = GET_P4REGPTR()->GetFontWeight();
	BOOL isItalic= GET_P4REGPTR()->GetFontItalic();

	MainFrame()->OnPerforceOptions(TRUE, FALSE, 0, IDS_PAGE_SPECIFICATIONS);

	if (face != GET_P4REGPTR()->GetFontFace()
	 || size != GET_P4REGPTR()->GetFontSize()
	 || weight != GET_P4REGPTR()->GetFontWeight()
	 || isItalic != GET_P4REGPTR()->GetFontItalic())
	{
		m_Font.DeleteObject();
		CreateTheFont();
	}
}

void CSpecDescDlg::OnFileRevisionhistory() 
{
	int i;
	int rev = -1;
	CString itemStr= m_Text.GetSelText();
	if ((i = itemStr.Find(_T('#'))) != -1)
	{
		rev = _ttoi(itemStr.Right(itemStr.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		itemStr = itemStr.Left(i);
	}
	
	CCmd_History *pCmd= new CCmd_History;
	pCmd->Init( MainFrame()->GetDepotWnd(), RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd->SetCallingWnd(m_hWnd);
	pCmd->SetInitialRev(rev, itemStr);
	if( pCmd->Run( LPCTSTR(itemStr)) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_REQUESTING_HISTORY) );
	}
	else
		delete pCmd;
}

void CSpecDescDlg::OnFileRevisionTree() 
{
	int i;
	int rev = -1;
	CString itemStr= m_Text.GetSelText();
	if ((i = itemStr.Find(_T('#'))) != -1)
	{
		rev = _ttoi(itemStr.Right(itemStr.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		itemStr = itemStr.Left(i);
	}
	TheApp()->CallP4RevisionTree(itemStr);	
}

void CSpecDescDlg::OnFileInformation() 
{
	int i;
	CString itemStr= m_Text.GetSelText();
	if ((i = itemStr.Find(_T('#'))) != -1)
		itemStr = itemStr.Left(i);

	m_StringList.RemoveAll();
	m_StringList.AddHead(m_ItemStr = itemStr);
	
	CCmd_Opened *pCmd= new CCmd_Opened;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK, m_Key);
	pCmd->SetAlternateReplyMsg( WM_P4FILEINFORMATION );

	if( pCmd->Run( TRUE, FALSE, -1, &m_StringList ) )
		UPDATE_STATUS( LoadStringResource(IDS_REQUESTING_FILE_INFORMATION) );
	else
		delete pCmd;
}

LRESULT CSpecDescDlg::OnP4FileInformation( WPARAM wParam, LPARAM lParam )
{
	CCmd_Opened *pCmd= (CCmd_Opened *) wParam;
	
	m_StringList.RemoveAll();
	if(!pCmd->GetError())
	{
		CString thisuser=GET_P4REGPTR()->GetMyID();
				
		// Initialize the file info dialog
		CFileInfoDlg *dlg = new CFileInfoDlg(this);

		dlg->m_DepotPath = m_ItemStr;

		int key= pCmd->GetServerKey();
		CCmd_Fstat *pCmd2= new CCmd_Fstat;
		
		pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK, key);
		if ( !PumpMessages( ) )
			goto CantGetFStat;

		pCmd2->SetIncludeAddedFiles( TRUE );
		if( pCmd2->Run( FALSE, m_ItemStr, TRUE, 0 ) && !pCmd2->GetError() )
		{
			CObList *list = pCmd2->GetFileList ( );
			ASSERT_KINDOF( CObList, list );
			ASSERT( list->GetCount() <= 1 );
			POSITION pos = list->GetHeadPosition( );
			if( pos != NULL )
			{
				CP4FileStats *stats = ( CP4FileStats * )list->GetNext( pos );
				ASSERT_KINDOF( CP4FileStats, stats );
				dlg->m_ClientPath = stats->GetFullClientPath( );
				if(dlg->m_ClientPath.GetLength() == 0)
					dlg->m_ClientPath= LoadStringResource(IDS_NOT_IN_CLIENT_VIEW);
				
				dlg->m_HeadRev.Format(_T("%ld"), stats->GetHeadRev());
				dlg->m_HaveRev.Format(_T("%ld"), stats->GetHaveRev());
				
				dlg->m_HeadAction= stats->GetActionStr(stats->GetHeadAction());
				dlg->m_HeadChange.Format(_T("%ld"), stats->GetHeadChangeNum());
				dlg->m_HeadType= stats->GetHeadType();
				dlg->m_ModTime= stats->GetFormattedHeadTime();
				dlg->m_FileSize= stats->GetFileSize();

				// Check for open/lock by this user
				if(stats->IsMyLock())
					dlg->m_LockedBy= thisuser;
				
				delete stats;
			}		
			else dlg->m_ClientPath= LoadStringResource(IDS_NOT_IN_CLIENT_VIEW);
		}

CantGetFStat:
		if (!m_Key)
			RELEASE_SERVER_LOCK(key);
		delete pCmd2;

		CObList *list= pCmd->GetList();
		ASSERT_KINDOF(CObList, list);

        POSITION pos= list->GetHeadPosition();
		while(pos != NULL)
		{
			CP4FileStats *fs= (CP4FileStats *) list->GetNext(pos);
			
			CString str;
			CString strUser;
			CString strChange;
			CString strAction;

			if( fs->GetOpenChangeNum() == 0 )
				strChange= LoadStringResource(IDS_DEFAULT_CHANGE);
			else
				strChange.FormatMessage(IDS_CHANGE_n, fs->GetOpenChangeNum()); 

			strUser= fs->GetOtherUsers();
			if( fs->IsMyOpen() && strUser.IsEmpty() )
			{
				strUser= thisuser;
				strAction= fs->GetActionStr(fs->GetMyOpenAction());
			}
			else
				strAction= fs->GetActionStr(fs->GetOtherOpenAction());

			str.Format(_T("%s - %s (%s)"), strUser, strChange, strAction);
			
			if( fs->IsOtherLock() )
				str += " " + LoadStringResource(IDS_STAR_LOCKED);
			
			dlg->m_StrList.AddHead( str );
			
			delete fs;
		}
		delete pCmd;		// no longer needed - delete it now before the dialog goes up
		// Display the info
		if (!dlg->Create(IDD_FILE_INFORMATION, this))	// display the description dialog box
		{
			dlg->DestroyWindow();	// some error! clean up
			delete dlg;
		}
	}
	else
		delete pCmd;

	UPDATE_STATUS(_T(""));
	
	return 0;
}

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

void CSpecDescDlg::OnPositionDepot()
{
	int i;
	CString itemStr = m_Text.GetSelText();
	if ((i = itemStr.Find(_T('#'))) != -1)
		itemStr = itemStr.Left( i );  // trim off rev# info
	else if ((i = itemStr.Find(_T("/..."))) != -1)
		itemStr = itemStr.Left( i );  // trim off "/..."
	((CMainFrame *) AfxGetMainWnd())->ExpandDepotString( itemStr, TRUE );
}

void CSpecDescDlg::OnDiffHead()
{
	int i;
	int rev = -1;
	int headRev = -1;
	CString fileType = _T("text");
	CString name = m_Text.GetSelText();
	if ((i = name.Find(_T('#'))) != -1)
	{
		rev = _ttoi(name.Right(name.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		name = name.Left( i );  // trim off rev# info
	}

	if ( !PumpMessages( ) )
		return;		// 0 -> quitting

	CCmd_Fstat *pCmd2= new CCmd_Fstat;
	pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd2->SetIncludeAddedFiles( TRUE );
	if( pCmd2->Run( FALSE, name, TRUE, 0 ) && !pCmd2->GetError() )
	{
		CObList *coblist2 = pCmd2->GetFileList( );
		ASSERT_KINDOF( CObList, coblist2 );
		ASSERT( coblist2->GetCount() <= 1 );
		POSITION pos = coblist2->GetHeadPosition( );
		if( pos != NULL )
		{
			CP4FileStats *stats = ( CP4FileStats * )coblist2->GetNext( pos );
			ASSERT_KINDOF( CP4FileStats, stats );
			fileType= stats->GetHeadType();
			headRev = stats->GetHeadRev();
			delete stats;
		}		
	}
	delete pCmd2;

	if ((rev == -1) || (headRev == -1))
	{
		CString txt = LoadStringResource(IDS_UNABLE_TO_DETERMINE_REV_NUMBERS_DIFF_FAILS);
		if (headRev == -1)
			txt += LoadStringResource(IDS_SPECDESC_FILE_NOT_IN_CLIENT_VIEW);
		AfxMessageBox(txt, MB_OK | MB_ICONHAND);
		return;
	}
	if (rev == headRev)
	{
		AfxMessageBox(IDS_THIS_IS_THE_HEAD_REV, MB_OK);
		return;
	}

	CCmd_Diff2 *pCmd= new CCmd_Diff2;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( name, name, rev, headRev, fileType, fileType) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_DIFFING_FILES) );
	}
	else
		delete pCmd;
}

void CSpecDescDlg::OnDiffPrev()
{
	int i;
	int rev = -1;
	int prevRev = -1;
	CString fileType = _T("text");
	CString name = m_Text.GetSelText();
	if ((i = name.Find(_T('#'))) != -1)
	{
		rev = _ttoi(name.Right(name.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		name = name.Left( i );  // trim off rev# info
	}
	if (rev < 2)
	{
		AfxMessageBox(rev == 1 ? 
            IDS_THERE_IS_NO_PREVIOUS_REV : 
            IDS_UNABLE_TO_DETERMINE_REV_NUMBER_DIFF_FAILS,
			MB_OK);
		return;
	}

	prevRev = rev - 1;

	if ( !PumpMessages( ) )
		return;		// 0 -> quitting

	CCmd_Fstat *pCmd2= new CCmd_Fstat;
	pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd2->SetIncludeAddedFiles( TRUE );
	if( pCmd2->Run( FALSE, name, TRUE, 0 ) && !pCmd2->GetError() )
	{
		CObList *coblist2 = pCmd2->GetFileList( );
		ASSERT_KINDOF( CObList, coblist2 );
		ASSERT( coblist2->GetCount() <= 1 );
		POSITION pos = coblist2->GetHeadPosition( );
		if( pos != NULL )
		{
			CP4FileStats *stats = ( CP4FileStats * )coblist2->GetNext( pos );
			ASSERT_KINDOF( CP4FileStats, stats );
			fileType= stats->GetHeadType();
			delete stats;
		}		
	}
	delete pCmd2;

	CCmd_Diff2 *pCmd= new CCmd_Diff2;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( name, name, prevRev, rev, fileType, fileType) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_DIFFING_FILES) );
	}
	else
		delete pCmd;
}

void CSpecDescDlg::OnDiffCliFile()
{
	int i;
	int rev = -1;
	CString fileType = _T("text");
	CString name = m_Text.GetSelText();
	CString clifile;
	if ((i = name.Find(_T('#'))) != -1)
	{
		rev = _ttoi(name.Right(name.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		name = name.Left( i );  // trim off rev# info
	}

	if ( !PumpMessages( ) )
		return;		// 0 -> quitting

	CCmd_Fstat *pCmd2= new CCmd_Fstat;
	pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd2->SetIncludeAddedFiles( TRUE );
	if( pCmd2->Run( FALSE, name, 0 ) && !pCmd2->GetError() )
	{
		CObList *coblist2 = pCmd2->GetFileList( );
		ASSERT_KINDOF( CObList, coblist2 );
		ASSERT( coblist2->GetCount() <= 1 );
		POSITION pos = coblist2->GetHeadPosition( );
		if( pos != NULL )
		{
			CP4FileStats *stats = ( CP4FileStats * )coblist2->GetNext( pos );
			ASSERT_KINDOF( CP4FileStats, stats );
			fileType= stats->GetHeadType();
			clifile = stats->GetFullClientPath();
			delete stats;
		}		
	}
	delete pCmd2;

	if (clifile.IsEmpty())
	{
		CString msg = name + _T(" ") + LoadStringResource(IDS_NOT_IN_CLIENT_VIEW);
		AfxMessageBox(msg, MB_ICONINFORMATION);
		return;
	}

	CCmd_Diff2 *pCmd= new CCmd_Diff2;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( name, clifile, rev, rev, fileType, fileType, FALSE, TRUE) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_DIFFING_FILES) );
	}
	else
		delete pCmd;
}

LRESULT CSpecDescDlg::OnP4Diff2(WPARAM wParam, LPARAM lParam)
{
	CCmd_Diff2 *pCmd= (CCmd_Diff2 *) wParam;
	CString msg= pCmd->GetInfoText();
	if( ! msg.IsEmpty() )
	{
		AfxMessageBox( msg, MB_ICONINFORMATION);
	}
	
	UPDATE_STATUS(_T(""));
	delete pCmd;
	return 0;
}

void CSpecDescDlg::OnUpdateSync(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fRev & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdatePositionDepot(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fFile & (MF_GRAYED|MF_DISABLED)) && !m_Key);
}


void CSpecDescDlg::OnUpdateDiffHead(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fRev & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateDiffPrev(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fRev & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateDiffCliFile(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fRev & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateFileAutobrowse(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fRev & (MF_GRAYED|MF_DISABLED)));
}                                        

void CSpecDescDlg::OnUpdateFileAnnotate(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(GET_SERVERLEVEL() >= 14 && !(m_fRev & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateFileInformation(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fProp & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateFileRevisionhistory(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fHist & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateDescChg(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fChg & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateDescBranch(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fItem & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateDescLabel(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fItem & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateDescClient(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fCli & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateDescUser(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fUser & (MF_GRAYED|MF_DISABLED)));
}


void CSpecDescDlg::OnUpdateDescJob(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fJob & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateCallTrack(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fClTk & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateEmail(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fEmail & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateURL(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fURL & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnUpdateEditCopy(CCmdUI* pCmdUI) 
{
    long nStartChar, nEndChar;
    m_Text.GetSel(nStartChar, nEndChar);
	pCmdUI->Enable(nStartChar < nEndChar);
}

void CSpecDescDlg::OnSync() 
{
	int i;
	CString rev;
	CString itemStr= m_Text.GetSelText();
	if ((i = itemStr.Find(_T('#'))) != -1)
	{
		rev = itemStr.Right(itemStr.GetLength() - i - 1);
		itemStr = itemStr.Left(i);
	}
	if (rev.GetLength() == 0)
	{
		AfxMessageBox(IDS_UNABLE_TO_DETERMINE_REV_NUMBER_SYNC_FAILS,
			MB_ICONSTOP);
		return;
	}
	itemStr += _T("#") + rev;

	m_StringList.RemoveAll();
	m_StringList.AddHead(itemStr);
	CCmd_Get *pCmd= new CCmd_Get;
	pCmd->Init( MainFrame()->GetDepotWnd(), RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( &m_StringList, FALSE ) )
		UPDATE_STATUS( LoadStringResource(IDS_FILE_SYNC) );
	else
		delete pCmd;
}

void CSpecDescDlg::OnFileAutobrowse() 
{
	int i;
	int rev = -1;
	CString fileType = _T("text");
	CString name = m_Text.GetSelText();
	if ((i = name.Find(_T('#'))) != -1)
	{
		rev = _ttoi(name.Right(name.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		name = name.Left( i );  // trim off rev# info
	}

	if ( !PumpMessages( ) )
		return;		// 0 -> quitting

	CCmd_Fstat *pCmd2= new CCmd_Fstat;
	pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd2->SetIncludeAddedFiles( TRUE );
	if( pCmd2->Run( FALSE, name, TRUE, 0 ) && !pCmd2->GetError() )
	{
		CObList *coblist2 = pCmd2->GetFileList( );
		ASSERT_KINDOF( CObList, coblist2 );
		ASSERT( coblist2->GetCount() <= 1 );
		POSITION pos = coblist2->GetHeadPosition( );
		if( pos != NULL )
		{
			CP4FileStats *stats = ( CP4FileStats * )coblist2->GetNext( pos );
			ASSERT_KINDOF( CP4FileStats, stats );
			fileType= stats->GetHeadType();
			delete stats;
		}
		else
		{
			AfxMessageBox(IDS_UNABLE_TO_DETERMINE_FILE_TYPE_VIEW_FAILS, MB_ICONSTOP);
			rev = -2;
		}
	}
	else
	{
		AfxMessageBox(IDS_UNABLE_TO_DETERMINE_FILE_TYPE_VIEW_FAILS, MB_ICONSTOP);
		rev = -2;
	}
	delete pCmd2;
	if (rev == -2)
		return;

	if (rev == -1)
	{
		AfxMessageBox(IDS_UNABLE_TO_DETERMINE_REV_NUMBER_VIEW_FAILS,
			MB_ICONSTOP);
		return;
	}

	// Ask the user to pick a viewer
	CViewerDlg dlg;
	SET_APP_HALTED(TRUE);
	if(dlg.DoModal() == IDCANCEL)
	{
		SET_APP_HALTED(FALSE);
		return;
	}
	SET_APP_HALTED(FALSE);
	m_Viewer=dlg.GetViewer();
	if(m_Viewer != _T("SHELLEXEC"))
		GET_P4REGPTR()->AddMRUViewer(m_Viewer);

	m_ViewFileIsText = ((fileType.Find(_T("text")) != -1) 
					 || (fileType.Find(_T("symlink")) != -1)) ? TRUE : FALSE;

	// Fetch the selected revision of the file to a temp filename
	CCmd_PrepBrowse *pCmd= new CCmd_PrepBrowse;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( name, fileType, rev ) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_FETCHING_FILE) );
	}
	else
		delete pCmd;
}

void CSpecDescDlg::OnFileAnnotate() 
{
	int i;
	int rev = -1;
	CString fileType = _T("text");
	CString name = m_Text.GetSelText();
	if ((i = name.Find(_T('#'))) != -1)
	{
		rev = _ttoi(name.Right(name.GetLength() - i - 1));
		if (!rev)
			 rev = -1;
		name = name.Left( i );  // trim off rev# info
	}

	if ( !PumpMessages( ) )
		return;		// 0 -> quitting

	if (MainFrame()->HaveTLV())
	{
		TheApp()->CallP4A(name, _T(""), 0);	// use p4v.exe for annotate
		return;
	}

	CCmd_Fstat *pCmd2= new CCmd_Fstat;
	pCmd2->Init(NULL, RUN_SYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	pCmd2->SetIncludeAddedFiles( TRUE );
	if( pCmd2->Run( FALSE, name, TRUE, 0 ) && !pCmd2->GetError() )
	{
		CObList *coblist2 = pCmd2->GetFileList( );
		ASSERT_KINDOF( CObList, coblist2 );
		ASSERT( coblist2->GetCount() <= 1 );
		POSITION pos = coblist2->GetHeadPosition( );
		if( pos != NULL )
		{
			CP4FileStats *stats = ( CP4FileStats * )coblist2->GetNext( pos );
			ASSERT_KINDOF( CP4FileStats, stats );
			fileType= stats->GetHeadType();
			delete stats;
		}
		else
		{
			AfxMessageBox(IDS_UNABLE_TO_DETERMINE_FILE_TYPE_VIEW_FAILS, MB_ICONSTOP);
			rev = -2;
		}
	}
	else
	{
		AfxMessageBox(IDS_UNABLE_TO_DETERMINE_FILE_TYPE_VIEW_FAILS, MB_ICONSTOP);
		rev = -2;
	}
	delete pCmd2;
	if (rev == -2)
		return;

	if (rev == -1)
	{
		AfxMessageBox(IDS_UNABLE_TO_DETERMINE_REV_NUMBER_VIEW_FAILS,
			MB_ICONSTOP);
		return;
	}

	m_ViewFileIsText = ((fileType.Find(_T("text")) != -1) 
					 || (fileType.Find(_T("symlink")) != -1)) ? TRUE : FALSE;

	// Fetch the annotated file to a temp filename
	CCmd_PrepBrowse *pCmd= new CCmd_PrepBrowse;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( FALSE, name, fileType, FALSE, FALSE, FALSE, rev, 
				GET_P4REGPTR()->GetAnnotateWhtSpace()) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_FETCHING_FILE) );
		m_Viewer=GET_P4REGPTR()->GetEditApp();
	}
	else
		delete pCmd;
}

// TODO: This code is pretty much a copy of the code in CDepotView::RunViewer()
// Might want to craft a single file viewing class that can be instantiated from
// anywhere, or perhaps make CMainFrame be responsible for all file viewing.
LRESULT CSpecDescDlg::OnP4ViewFile(WPARAM wParam, LPARAM lParam)
{
	UPDATE_STATUS(_T(""));
	CString tempName;
	CString msg;

	CCmd_PrepBrowse *pCmd= (CCmd_PrepBrowse *) wParam;

	if(!pCmd->GetError())
	{
		CString viewFilePath= pCmd->GetTempName();

		// First, get the file extension, if any, and find out if
		// its a text file
		CString extension;
		int slash= viewFilePath.ReverseFind(_T('\\'));
		if(slash != -1)
			extension=viewFilePath.Mid(slash+1);
		else
			extension=viewFilePath;

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

		// We have the file, viewFilePath, try to display it
		while(1)
		{
			if(m_Viewer == _T("SHELLEXEC"))
			{
				CString assocViewer;

				// First, see if there a P4win file association
				if(!extension.IsEmpty())
					assocViewer= GET_P4REGPTR()->GetAssociatedApp(extension);
			
				// If we still havent found a viewer, set viewer to default text app
				// if user wishes to ignore windows associations
				if(assocViewer.IsEmpty() && m_ViewFileIsText && GET_P4REGPTR()->GetIgnoreWinAssoc())
					assocViewer= GET_P4REGPTR()->GetEditApp();
			
				// Let windows take a crack at finding a viewer
				if(assocViewer.IsEmpty() && !extension.IsEmpty())
				{
					// Quick check for executeable extension, which will make ShellExec try to run the file
					HINSTANCE hinst=0;
					if( extension.CompareNoCase(_T("com")) != 0 && extension.CompareNoCase(_T("exe")) != 0 &&
						extension.CompareNoCase(_T("bat")) != 0 && extension.CompareNoCase(_T("cmd")) != 0)
					{										// give VS .NET 7.1 (non-standard!) a try
						hinst= ShellExecute( m_hWnd, _T("Open.VisualStudio.7.1"), viewFilePath, NULL, NULL, SW_SHOWNORMAL);
						if( (int) hinst > 32)
						{
							break;  // successfull viewer launch
						}
						if( (int) hinst == SE_ERR_NOASSOC)	// give MSDEV (non-standard!) a try
						{
							hinst= ShellExecute( m_hWnd, _T("&Open with MSDEV"), viewFilePath, NULL, NULL, SW_SHOWNORMAL);
							if( (int) hinst > 32 ) 
								break;  // successfull MSDEV viewer launch
						}
						if( (int) hinst == SE_ERR_NOASSOC)	// give standard "open" a try
						{
							hinst= ShellExecute( m_hWnd, _T("open"), viewFilePath, NULL, NULL, SW_SHOWNORMAL);
							if( (int) hinst > 32 ) 
								break;  // successfull MSDEV viewer launch
						}
					}
				}

				// If windows doesnt have an associated viewer for a text file, we use the 
				// default text editor
				if(assocViewer.IsEmpty() && m_ViewFileIsText)
					assocViewer= GET_P4REGPTR()->GetEditApp();
				

				if ( TheApp()->RunViewerApp( assocViewer, viewFilePath ) )
					break;  // successfull viewer launch
			}
			else
			{
				if ( TheApp()->RunViewerApp( m_Viewer, viewFilePath ) )
					break;  // successfull viewer launch
			}

			CString msg;
			msg.FormatMessage(IDS_UNABLE_TO_LAUNCH_VIEWER_s, viewFilePath);
			if(AfxMessageBox(msg, MB_YESNO | MB_ICONEXCLAMATION) == IDNO)
				break;

			// Try to find an alternate viewer
			CViewerDlg dlg;
			SET_APP_HALTED(TRUE);
			if(dlg.DoModal() == IDCANCEL)
			{
				SET_APP_HALTED(FALSE);
				break;
			}

			SET_APP_HALTED(FALSE);
			m_Viewer=dlg.GetViewer();
			if(m_Viewer != _T("SHELLEXEC"))
				GET_P4REGPTR()->AddMRUViewer(m_Viewer);
		} // while
	} // no command error
	
	delete pCmd;
	UPDATE_STATUS(_T(""));
	return 0;
}

void CSpecDescDlg::OnDescItem(HWND hWnd, int viewType, int flag /*=0*/)
{
	m_SelItem.TrimRight();
	m_SelItem.TrimLeft();
	CCmd_Describe *pCmd = new CCmd_Describe;
	// if a command is in progress, the new dialogs are modeless but their parent
	// needs to be this dialog for proper cleanup if the user does something stupid.
	pCmd->Init( SERVER_BUSY() ? m_hWnd : hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key );
	if( pCmd->Run( m_SelType = viewType, m_SelItem, NULL, FALSE, flag ) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_FETCHING_SPEC) );	
		return;
	}
	else
	{
		delete pCmd;
		return;
	}
}

void CSpecDescDlg::OnDescChg()
{
	m_SelItem = m_Text.GetSelText();
	OnDescItem(MainFrame()->OldChgsWnd(), P4DESCRIBE);
}

void CSpecDescDlg::OnDescChgLong(int flag)
{
	OnDescItem(MainFrame()->OldChgsWnd(), P4DESCRIBELONG, flag);
}

void CSpecDescDlg::OnDescBranch()
{
	m_SelItem = m_Text.GetSelText();
	OnDescItem(MainFrame()->BranchWnd(), P4BRANCH_SPEC);
}

void CSpecDescDlg::OnDescLabel()
{
	m_SelItem = m_Text.GetSelText();
	OnDescItem(MainFrame()->LabelWnd(), P4LABEL_SPEC);
}

void CSpecDescDlg::OnDescClient()
{
	int i;
	m_SelItem = m_Text.GetSelText();
	if ((i = m_SelItem.Find(_T('@'))) != -1)
		m_SelItem = m_SelItem.Right(m_SelItem.GetLength() - i - 1);
	OnDescItem(MainFrame()->ClientWnd(), P4CLIENT_SPEC);
}

void CSpecDescDlg::OnDescUser()
{
	int i;
	m_SelItem = m_Text.GetSelText();
	if ((i = m_SelItem.Find(_T('@'))) != -1)
		m_SelItem = m_SelItem.Left(i);
	OnDescItem(MainFrame()->UserWnd(), P4USER_SPEC);
}

void CSpecDescDlg::OnDescJob()
{
	m_SelItem = m_Text.GetSelText();
	OnDescItem(MainFrame()->JobWnd(), P4JOB_SPEC);
}

void CSpecDescDlg::OnUpdateDiff2(CCmdUI* pCmdUI) 
{
    SetMenuFlags();
	pCmdUI->Enable(!(m_fDiff2 & (MF_GRAYED|MF_DISABLED)));
}

void CSpecDescDlg::OnDiff2()
{
	TCHAR szBuffer[4096];
	LPTSTR lpszBuffer = szBuffer;
	CString curLine;
	long nStartChar;
	long nEndChar;

	m_Text.GetSel(nStartChar, nEndChar);
	long lineNbr = m_Text.LineFromChar(-1);
	int lgth = m_Text.GetLine(lineNbr, lpszBuffer, sizeof(szBuffer)/sizeof(TCHAR) - 6);
	szBuffer[lgth] = _T('\0');
	curLine = &szBuffer[0];
	while (curLine && curLine.GetAt(0) != _T('=') || curLine.Find(_T("//")) == -1)
	{
		CString tempLine = curLine;		// temp variable needed becuase of MFC bug
		lgth = m_Text.GetLine(--lineNbr, lpszBuffer, sizeof(szBuffer)/sizeof(TCHAR) - 6);
		szBuffer[lgth] = _T('\0');
		curLine = &szBuffer[0] + tempLine;
	}
	curLine.TrimLeft(_T(" ="));
	ASSERT(curLine.GetAt(0) == _T('/'));

	int i;
	if ((i = curLine.Find(_T('#'))) > 0)
	{
		CString file1 = curLine.Left(i);
		curLine = curLine.Mid(i+1);
		int rev1 = _tstoi(curLine);
		if ((i = curLine.Find(_T('('))) > 0)
		{
			CString ft1 = curLine = curLine.Mid(i+1);
			if ((i = ft1.Find(_T(')'))) > 0)
			{
				ft1 = ft1.Left(i);
				if ((i = curLine.Find(_T('/'))) > 0)
				{
					curLine = curLine.Mid(i);
					if ((i = curLine.Find(_T('#'))) > 0)
					{
						CString file2 = curLine.Left(i);
						curLine = curLine.Mid(i+1);
						int rev2 = _tstoi(curLine);
						if ((i = curLine.Find(_T('('))) > 0)
						{
							CString ft2 = curLine.Mid(i+1);
							if ((i = ft2.Find(_T(')'))) > 0)
							{
								ft2 = ft2.Left(i);
								CCmd_Diff2 *pCmd= new CCmd_Diff2;
								pCmd->Init( MainFrame()->GetDepotWnd(), RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key );
								if( pCmd->Run( file1, file2, rev1, rev2, ft1, ft2, FALSE, FALSE) )
									MainFrame()->UpdateStatus( LoadStringResource(IDS_DIFFING_FILES) );
								else
									delete pCmd;
								return;
							}
						}
					}
				}
			}
		}
	}
	AfxMessageBox(_T("Error parsing line\nUnable to determine files to diff"), MB_ICONSTOP);
}

LRESULT CSpecDescDlg::OnP4Describe( WPARAM wParam, LPARAM lParam )
{
	// Modal SpecDescDlgs are only called when the server is busy
	if (!SERVER_BUSY())
		ASSERT(0);

	MSG  msg;
	BOOL ret = FALSE;
	BOOL bOK = TRUE;
	CString txt;

	CCmd_Describe *pCmd = ( CCmd_Describe * )wParam;

	if(!pCmd->GetError() && !MainFrame()->IsQuitting())
	{
		CString desc = MakeCRs( pCmd->GetDescription( ) );

		if (m_SelType == P4JOB_SPEC)
		{
			int i, j = -2;
	        CString specblankdesc = _T("<enter description here>");
			if ((i = desc.Find(specblankdesc)) > 0)
			{
				j = lstrlen(_T("\r\nDescription:\r\n\t"));
				j = desc.Find(_T("\r\nDescription:\r\n\t"), i - j*2) + j;
			}
			if ((i == j) 
			 && (desc.GetAt(i-1) == _T('\t'))
			 && ((i+specblankdesc.GetLength() >= desc.GetLength()) 
			  || (desc.GetAt(i+specblankdesc.GetLength()) < _T(' '))))
			{
				txt.FormatMessage(IDS_THERE_IS_NO_s_TO_DESCRIBE, m_SelItem);
 				bOK = FALSE;
			}
		}
		else if (((m_SelType == P4BRANCH_SPEC) || (m_SelType == P4LABEL_SPEC) 
			   || (m_SelType == P4CLIENT_SPEC) || (m_SelType == P4USER_SPEC)) 
			  && (desc.Find(_T("\nUpdate:")) == -1))
		{
			txt = m_SelItem + _T(" - no such ");
			switch(m_SelType)
			{
			case P4BRANCH_SPEC:
				txt += _T("branch");
				break;
			case P4LABEL_SPEC:
				txt += _T("label");
				break;
			case P4CLIENT_SPEC:
				txt += _T("client");
				break;
			case P4USER_SPEC:
				txt += _T("user");
				break;
			}
			bOK = FALSE;
		}
		if (bOK)
		{
			CSpecDescDlg *dlg = new CSpecDescDlg(this);
			dlg->SetIsModeless(TRUE);
			dlg->SetKey(m_Key);
			dlg->SetDescription( desc );
			dlg->SetItemName( m_SelItem );
            CString caption;
            caption.FormatMessage(IDS_PERFORCE_DESCRIPTION_FOR_s, m_SelItem);
			dlg->SetCaption( caption );
			dlg->SetShowNextPrev(FALSE);
			dlg->SetShowShowDiffs(m_SelType == P4DESCRIBE || m_SelType == P4DESCRIBELONG);
			dlg->SetDiffFlag(pCmd->GetFlag());
			dlg->SetShowEditBtn(m_SelType == P4JOB_SPEC);
			dlg->SetShowShowFixes(m_SelType == P4JOB_SPEC);
			dlg->SetShowShowFiles(m_SelType == P4LABEL_SPEC);
			dlg->SetFindStrFlags(&m_FindWhatStr, m_FindWhatFlags);
			dlg->SetViewType(m_SelType);
			while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )	// clear the message queue
			{
				if ( msg.message == WM_QUIT )	//	get out if app is terminating
					break;
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			if (!dlg->Create(IDD_SPECDESC, this))	// display the description dialog box
			{
				dlg->DestroyWindow();	// some error! clean up
				delete dlg;
			}
		}
		else
		{
			EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE);
			AfxMessageBox(txt, MB_ICONWARNING);
		}
	}
	else	// had an error - need to turn painting back on
	{
		EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE);
		::InvalidateRect(NULL, NULL, TRUE);	// Make sure ALL windows get updated.
	}
	
	delete pCmd;
	UPDATE_STATUS( _T("") );
	return ret;
}

LRESULT CSpecDescDlg::OnP4EndDescribe(WPARAM wParam, LPARAM lParam)
{
	CSpecDescDlg *dlg = (CSpecDescDlg *)lParam;

	switch(wParam)				// which button did they click to close the box?
	{
	case ID_SHOWDIFFS_NORMAL:
	case ID_SHOWDIFFS_SUMMARY:
	case ID_SHOWDIFFS_UNIFIED:
	case ID_SHOWDIFFS_CONTEXT:
	case ID_SHOWDIFFS_RCS:
	case ID_SHOWDIFFS_NONE:
	{
		OnDescChgLong(wParam);
		break;
	}
	case IDC_EDITIT:
		if (!m_Key && wParam == IDC_EDITIT)
		{
			if (m_SelType == P4JOB_SPEC)
				MainFrame()->EditJobSpec(&m_SelItem);
			else
				ASSERT(0);
		}
	default:	// clicked OK, pressed ESC or ENTER - need to turn painting back on
		EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE);
		break;
	}
	dlg->DestroyWindow();
	return TRUE;
}

void CSpecDescDlg::OnEmail()
{
	CString seltxt = m_Text.GetSelText();
	seltxt.TrimRight(_T('>'));
	seltxt.TrimLeft(_T('<'));
	seltxt.TrimLeft();
	CString txt = _T("mailto:") + seltxt;
	int s = (int)ShellExecute(m_hWnd,_T("open"), txt,NULL,NULL,SW_SHOWNORMAL);
	if (s <= 32)
	{
		txt.FormatMessage(IDS_UNABLE_TO_SEND_EMAIL_TO_s, m_Text.GetSelText());
		AfxMessageBox(txt, MB_ICONSTOP);
	}
}

void CSpecDescDlg::OnURL()
{
	CString seltxt = m_Text.GetSelText();
	seltxt.TrimRight(_T(">\""));
	seltxt.TrimLeft(_T("<\""));
	seltxt.TrimLeft();
	int s = (int)ShellExecute(m_hWnd, _T("open"), seltxt,NULL,NULL,SW_SHOWNORMAL);
	if (s <= 32)
	{
		seltxt.FormatMessage(IDS_UNABLE_TO_BROWSE_s, m_Text.GetSelText());
		AfxMessageBox(seltxt, MB_ICONSTOP);
	}
}

void CSpecDescDlg::OnCallTrack()
{
	CString host = AfxGetApp()->GetProfileString(_T("Settings"), _T("CallTrackHost"), _T("calltrack"));
	CString txt = "http://" + host + _T("/cgi-bin/detail?call=") + m_Text.GetSelText();
	int s = (int)ShellExecute(m_hWnd,_T("open"), txt,NULL,NULL,SW_SHOWNORMAL);
	if (s <= 32)
	{
		txt = _T("Unable to display CallTrack number ") + m_Text.GetSelText();
		AfxMessageBox(txt, MB_ICONSTOP);
	}
}

void CSpecDescDlg::OnShowfixes() 
{
	CCmd_Fixes *pCmdFixes= new CCmd_Fixes;
	pCmdFixes->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmdFixes->Run(0, 0, TRUE, m_Item) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_UPDATING_JOB_FIXES) );
	}
	else
	{
		delete pCmdFixes;
		RedrawWindow();
		UPDATE_STATUS(_T(""));
	}
}

LRESULT CSpecDescDlg::OnP4Fixes(WPARAM wParam, LPARAM lParam)
{
	CCmd_Fixes *pCmd= (CCmd_Fixes *) wParam;

	if(!pCmd->GetError())
	{
		CObList *fixes = pCmd->GetList();
		CString theFixes;
		if (fixes->IsEmpty())
		{
			theFixes = LoadStringResource(IDS_EMPTY_FIXLIST);
		}
		else
		{
			CP4Fix *fix;
            CString buf;

			UpdateData(TRUE);
			theFixes = LoadStringResource(IDS_FIXLIST_HEADER);

			POSITION pos;
			for(pos= fixes->GetHeadPosition(); pos != NULL; )
			{
				fix=(CP4Fix *) fixes->GetNext(pos);
				ASSERT(fix->IsKindOf(RUNTIME_CLASS(CP4Fix)));
				
				buf.FormatMessage(IDS_FIXLIST_ITEM_n_ON_s_BY_s, fix->GetChangeNum(), 
								fix->GetFixDate(), fix->GetUser());
				theFixes += buf;
				delete fix;
			} //for
		}

        theFixes.Replace(_T("\r\n"), _T("\n"));
		m_Description += theFixes;
        SetEditText();
		m_Text.LineScroll(0 - m_Text.GetFirstVisibleLine());
		if( m_ScrollPastComments )
			ScrollPastComments();
		SetHotSpots();
		UpdateData(FALSE);
		GetDlgItem(IDC_SHOWFIXES)->ShowWindow(SW_HIDE);
		GetDlgItem(IDC_SHOWFIXES)->EnableWindow(FALSE);
	}
	
	UPDATE_STATUS(_T(""));
		
	delete pCmd;
	return 0;
}

void CSpecDescDlg::OnShowfiles() 
{
	if (SERVER_BUSY())
	{
		MessageBeep(0);
		return;
	}

	m_LabelFileCount=0;
	m_LabelFiles.Empty();

	CString spec;
	spec.Format(_T("//...@%s"), m_Item);

	// Call Fstat, w/ suppress==FALSE
	CCmd_Fstat *pCmd= new CCmd_Fstat;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);

	//		okay, this is weird, but let's set show entire depot
	//		to true, since we want this command to 
	//		read 'p4 fstat //...@mynumber WITHOUT the -C
	//		that would run otherwise. 
	//		after all, we all the files to show, not just
	//		the ones on the client view.
	//
	BOOL bshowEntireDepot = TRUE;
	if( pCmd->Run( FALSE, spec, bshowEntireDepot, 0 ) )
	{
		MainFrame()->UpdateStatus( LoadStringResource(IDS_REQUESTING_LABEL_CONTENTS) );
	}
	else
	{
		delete pCmd;
		MainFrame()->ClearStatus();
	}
}

LRESULT CSpecDescDlg::OnP4LabelContents(WPARAM wParam, LPARAM lParam)
{
	CString tmp;
    CCmd_Fstat *pCmd;

	if(lParam == 0)   // completion
	{
		pCmd= (CCmd_Fstat *) wParam;
		ASSERT_KINDOF(CCmd_Fstat,pCmd);

		if(!pCmd->GetError())
		{
			tmp.FormatMessage(IDS_LABEL_s_POINTS_TO_n_FILES, m_Item, m_LabelFileCount);
			AddToStatus(tmp, SV_COMPLETION);

			MainFrame()->ClearStatus();
			delete pCmd;
			if (m_LabelFileCount)
			{
				m_LabelFiles += _T('\n') + tmp;
				CSpecDescDlg *dlg = new CSpecDescDlg(this);
				dlg->SetIsModeless(TRUE);
				dlg->SetKey(m_Key);
				dlg->SetDescription( m_LabelFiles );
				dlg->SetItemName( m_Item );
                CString caption;
                caption.FormatMessage(IDS_FILE_LIST_FOR_LABEL_s, m_Item);
				dlg->SetCaption( caption );
				dlg->SetFindStrFlags(&m_FindWhatStr, m_FindWhatFlags);
				dlg->SetViewType(P4LABEL_SPEC);
				if (!dlg->Create(IDD_SPECDESC, this))	// display the description dialog box
				{
					dlg->DestroyWindow();	// some error! clean up
					delete dlg;
				}
				m_LabelFiles.Empty();
			}
		}
		return 0;
	}
	else
	{
        // Pull a ptr to the command, as well as a batch of CP4FileStats
        // out of the wrapper
        CFstatWrapper *pWrap= (CFstatWrapper *) wParam;
        pCmd= (CCmd_Fstat *) pWrap->pCmd;
	    ASSERT_KINDOF(CCmd_Fstat, pCmd);
		CObList *list= (CObList *) pWrap->pList;
		ASSERT_KINDOF(CObList, list);
        
		POSITION pos= list->GetHeadPosition();
		while(pos != NULL)
		{
			// Get the filestats
			CP4FileStats *stats= (CP4FileStats *) list->GetNext(pos);
			ASSERT_KINDOF(CP4FileStats, stats);
		
			// Increment the counter
			m_LabelFileCount++;

			// Format the file, rev and type
			tmp.FormatMessage(IDS_s_n_s_CHANGELIST_n_s, 
				stats->GetFullDepotPath(),
				stats->GetHeadRev(),
				stats->GetHeadType(),
				stats->GetHeadChangeNum(),
				stats->GetActionStr(stats->GetHeadAction()));

			// And add to Description
			m_LabelFiles += tmp;

			delete stats;

		} // while row batch not done

		delete list;
		delete pWrap;
		return 0;
	} // a batch of rows, we'll be called again so dont delete pCmd
}

BOOL CSpecDescDlg::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	OnHelp();
	return(FALSE);
}

void CSpecDescDlg::OnQuickHelp()
{
	AfxMessageBox(IDS_SPECDESC_QUICKHELP, MB_ICONINFORMATION);
}

void CSpecDescDlg::OnHelp()
{
	AfxGetApp()->WinHelp(TASK_GETTING_INFORMATION_ABOUT_FORMS);
}

int
CSpecDescDlg::AddHotSpotWord(int idx, int offset, int lineStart, int lgth, BOOL bAtSign)
{
    LPCWSTR line = m_DescriptionW + lineStart;

    // scan backwards from offset until a space or line ending is found
    LPCWSTR pBegin = line + offset - lineStart;
    while(*pBegin > ' ' && pBegin > line)
	{
		if (bAtSign && ((*pBegin == '\\') || (*pBegin == '/') || (*pBegin == ':')))
			return offset;	// bail because users, clients and email addrs don't have /, \ or :
		if (bAtSign && (*pBegin == '<'))
			break;	// because '<' terminates email and can't appear in users & clients
        pBegin--;
	}
    if(*pBegin <= ' ')
        pBegin++;

    // scan forwards from offset until space of line ending is found
    LPCWSTR pEnd = line + offset - lineStart;
    while(*pEnd > ' ')
	{
		if (bAtSign && ((*pEnd == '\\') || (*pEnd == '/') || (*pEnd == ':')))
			return offset;	// bail because users, clients and email addrs don't have /, \ or :
		if (bAtSign && (*pEnd == '>'))
			break;	// because '>' terminates email and can't appear in users & clients
        pEnd++;
	}

    // strip off angle brackets, if present
    if(*pBegin == '<')
    {
        pBegin++;
        if(*(pEnd-1) == '>')
            pEnd--;
    }

    // strip off parens, even if only left or right is present
    if(*pBegin == '(')
        pBegin++;
    if(*(pEnd-1)  == ')')
        pEnd--;

    // calculate indexes, and add hotspot
    int b = pBegin - line + lineStart;
    int e = pEnd - line + lineStart;
	m_HotSpotBgn.InsertAt(idx, b);
	m_HotSpotEnd.InsertAt(idx, e);
	return(e);
}

int
CSpecDescDlg::AddHotSpotFile(int idx, int offset, int lineStart, int lgth, BOOL bQuoted)
{
    // b points to either "// or -// or //

    int b = offset;
	int n = wcslen(m_TO_);
    if(m_DescriptionW[b] != '/')
        b++;

    LPCWSTR pEnd;
    if(bQuoted)
    {
        // scan forwards until first of: end of buffer, ending quote, or newline
        pEnd = m_DescriptionW + b + 2;
        while(*pEnd && *pEnd != '\"' && *pEnd >= ' ')
            pEnd++;
    }
    else
    {
    	BOOL bGotStop = FALSE;
        // start with pEnd after slashes
        for(pEnd = m_DescriptionW + b + 2; *pEnd; pEnd++)
	    {
            LPCWSTR pPrev = pEnd - 1;
			if (*pEnd == '#')
				bGotStop = TRUE;
			else if (*pEnd < ' ')
			{
                while(*pPrev == ' ' && pPrev >= m_DescriptionW + lineStart)
                    pEnd = pPrev--;
				break;
			}
			else if ((pEnd[0] == '.')
				  && (pEnd[1] == '.')
				  && (pEnd[2] == '.'))
			{
				bGotStop = TRUE;
                pEnd += 2;
			}
			else if ((pEnd[0] == '/')
				  && (pEnd[1] == '/'))
			{
                while(*pPrev == ' ' && pPrev >= m_DescriptionW + lineStart)
                    pEnd = pPrev--;
				break;
			}
			else if ((*pEnd == ' ') && !wcsncmp( pEnd, m_TO_, n ) 
				  && ((pEnd[n] == '/') || (pEnd[n] <= ' ')))
			{
				while(*pPrev == ' ' && pPrev >= m_DescriptionW + lineStart)
                    pEnd = pPrev--;
				break;
			}
			else if (bGotStop && (*pEnd == ' '))
				break;
    	}
    }
    int e = pEnd - m_DescriptionW;
	m_HotSpotBgn.InsertAt(idx, b);
	m_HotSpotEnd.InsertAt(idx, e);
	return(e);
}
#define STRLENW(s) (sizeof(s)/sizeof(WCHAR)-1)

void CSpecDescDlg::SetHotSpots()
{
    int lgth = wcslen(m_DescriptionW);
    LPCWSTR pLastMinus1 = m_DescriptionW + (lgth ? lgth - 1 : 0);
	BOOL bInJobs = FALSE;
	BOOL bInFixes = FALSE;
	BOOL bInDiffs = FALSE;

    // preallocate arrays, and set grow-by size
    m_HotSpotBgn.RemoveAll();
    m_HotSpotEnd.RemoveAll();
    m_HotSpotType.RemoveAll();
	m_HotSpotBgn.SetSize(m_Text.GetLineCount() - m_Text.GetFirstVisibleLine(), 10);
	m_HotSpotEnd.SetSize(m_Text.GetLineCount() - m_Text.GetFirstVisibleLine(), 10);
	m_HotSpotType.SetSize(m_Text.GetLineCount() - m_Text.GetFirstVisibleLine(), 10);
    m_numHotSpots = 0;

    int beginChar = m_Text.LineIndex();

	for (LPCWSTR pAt = m_DescriptionW + beginChar; *pAt; pAt++ )
	{
        // some strings we look for:
        static const WCHAR strModifiedBy[] = L"ModifiedBy:\t";
        static const WCHAR strUser[] = L"User:\t";
        static const WCHAR strJobsFixed[] = L"Jobs fixed ...";
        static const WCHAR strAffectedFilesEnglish[] = L"Affected files ...";
        static WCHAR strAffectedFiles[100] = L"";
        static WCHAR strFixes[100] = L"";
        static WCHAR strChange[100] = L"";
        static WCHAR strReportedBy[100] = L"";
        static const WCHAR strDifferences[] = L"Differences ...";
        static const WCHAR strChangelist[] = L"Changelist ";
		static const WCHAR strEqContent[] = L"==== content";

        static const int strModifiedByLen = STRLENW(strModifiedBy);
        static const int strUserLen = STRLENW(strUser);
        static const int strJobsFixedLen = STRLENW(strJobsFixed);
        static const int strAffectedFilesEnglishLen = STRLENW(strAffectedFilesEnglish);
        static int strAffectedFilesLen = STRLENW(strAffectedFiles) + 1;
        static int strFixesLen = STRLENW(strFixes) + 1;
        static int strChangeLen = STRLENW(strChange) + 1;
        static int strReportedByLen = STRLENW(strReportedBy) + 1;
        static const int strDifferencesLen = STRLENW(strDifferences);
        static const int strChangelistLen = STRLENW(strChangelist);
        static const int strEqContentLen = STRLENW(strEqContent);

        // load resource dependent tags first time through:
        static bool bResStringsLoaded = false;
        if(!bResStringsLoaded)
        {
            bResStringsLoaded = true;
            CString strAffectedFilesR = LoadStringResource(IDS_AFFECTEDFILES);
            strAffectedFilesR.TrimLeft(_T("\r\n"));
            strAffectedFilesR.TrimRight(_T("\r\n"));
            CString strFixesR = LoadStringResource(IDS_FIXLIST_HEADER);
            strFixesR.TrimLeft(_T('\n'));
            strFixesR.TrimRight(_T('\n'));
            CString strChangeR = LoadStringResource(IDS_FIXLIST_ITEM_CHANGE_TAG);
			CString strReportedByR;
			if (m_ReportedByTitle.IsEmpty())
				strReportedByR = _T("Owner:\t");
			else
				strReportedByR = m_ReportedByTitle + _T(":\t");
#ifdef UNICODE
            lstrcpy(strAffectedFiles, strAffectedFilesR);
            lstrcpy(strFixes, strFixesR);
            lstrcpy(strChange, strChangeR);
            lstrcpy(strReportedBy, strReportedByR);
#else
            ASSERT(strAffectedFilesR.GetLength() < strAffectedFilesLen);
            MultiByteToWideChar(CP_ACP, 0, strAffectedFilesR, -1, strAffectedFiles, strAffectedFilesLen);
            ASSERT(strFixesR.GetLength() < strFixesLen);
            MultiByteToWideChar(CP_ACP, 0, strFixesR, -1, strFixes, strFixesLen);
            ASSERT(strChangeR.GetLength() < strChangeLen);
            MultiByteToWideChar(CP_ACP, 0, strChangeR, -1, strChange, strChangeLen);
            ASSERT(strReportedByR.GetLength() < strReportedByLen);
            MultiByteToWideChar(CP_ACP, 0, strReportedByR, -1, strReportedBy, strReportedByLen);
#endif
            strAffectedFilesLen = wcslen(strAffectedFiles);
            strFixesLen = wcslen(strFixes);
            strChangeLen = wcslen(strChange);
			strReportedByLen = wcslen(strReportedBy);
        }

        // if at end of line, skip to start of next line
        if(*pAt == '\n')
        {
            while(*pAt == '\n')
                pAt++;
            if(!*pAt)
                break;

            beginChar = pAt - m_DescriptionW;
        }

        int i = pAt - m_DescriptionW;

        if(bInDiffs && i == beginChar)
        {
            if(wcsncmp(pAt, L"==== ", 5))
            {
                // skip line if not starting a diff
                LPCWSTR pChar = pAt;
                while ((++pChar < pLastMinus1) && (*pChar != '\n'))
                    ;
                pAt = pChar - 1;
                continue;
            }
            i += 4;
            pAt += 4;
        }
        // an '@' that's not at the start of a line?
		if (i > beginChar && (*pAt == '@'))
		{
			// Don't put hotspots on @@
			if (*(pAt+1) == '@')
			{
				pAt++;
				continue;
			}

            // make sure there's something before the '@'
            LPCWSTR pBeforeAt = pAt-1;
            if(*pBeforeAt <= ' ')
                continue;

			int newi = AddHotSpotWord(m_numHotSpots, i, beginChar, lgth, TRUE);
			if (newi == i)
				pAt++;	// word contained a / \ or : which is not in a user, client or email addr - so skip it
			else
			{
				LPCWSTR pDot = wcschr(m_DescriptionW+i+1, '.');
				if(pDot && pDot < m_DescriptionW+newi)
				{
					// found '.' following '@', so assume email address
					m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAEMAIL);
				}
				else
				{
					// no '.' following '@', so assume user@client
					m_HotSpotEnd.SetAt(m_numHotSpots, i);
					m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAUSER);
					m_HotSpotBgn.InsertAt(m_numHotSpots, i+1);
					m_HotSpotEnd.InsertAt(m_numHotSpots, newi);
					m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISACLIENT);
				}
				pAt += newi - i - 1;
			}
		}
        else if(!wcsncmp(pAt, L"-//", 3) || 
                !wcsncmp(pAt, L"\"//", 3) ||
                !wcsncmp(pAt, L" //", 3) ||
                !wcsncmp(pAt, L"\t//", 3) ||
                ((i == beginChar) && !wcsncmp(pAt, L"//", 2)))
        {
			if (*(pAt+3) <= ' ')	// depot names don't have a white space after the //
				continue;
            bool bQuoted = *pAt == L'\"';
   			int newi = AddHotSpotFile(m_numHotSpots, i, beginChar, lgth, bQuoted);
    		m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAFILE);
            // don't lose trailing delimiter
            pAt += newi - i - 1;
        }
        else if(!wcsncmp(pAt, L"http://", 7) || !wcsncmp(pAt, L"https://", 8))
        {
    		int newi = AddHotSpotWord(m_numHotSpots, i, beginChar, lgth, FALSE) - 1;
	    	m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAURL);
            pAt += newi - i;
        }
        else if(i == beginChar)
        {
            int newi = i;
            if(!wcsncmp(pAt, strReportedBy, strReportedByLen))
            {
                newi = AddHotSpotWord(m_numHotSpots, i + strReportedByLen, beginChar, lgth, FALSE);
                m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAUSER);
            }
            else if(!wcsncmp(pAt, strModifiedBy, strModifiedByLen))
            {
                newi = AddHotSpotWord(m_numHotSpots, i + strModifiedByLen, beginChar, lgth, FALSE);
                m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAUSER);
            }
            else if((m_viewType != P4USER_SPEC) && !wcsncmp(pAt, strUser, strUserLen))
            {
                newi = AddHotSpotWord(m_numHotSpots, i + strUserLen, beginChar, lgth, FALSE);
                m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAUSER);
            }
            else if(!wcsncmp(pAt, strJobsFixed, strJobsFixedLen))
            {
                bInJobs = TRUE;
                newi = i + strJobsFixedLen;
            }
            // note: depending on whether a submitted or unsubmitted change is 
            // being described, the 'Affected files...' string comes either
            // from the server (always English) or from our resource 
            // stringtable.  We don't know which it is here, so try both.
            else if(!wcsncmp(pAt, strAffectedFiles, strAffectedFilesLen))
            {
                bInJobs = FALSE;
                newi = i + strAffectedFilesLen;
            }
            else if(!wcsncmp(pAt, strAffectedFilesEnglish, strAffectedFilesEnglishLen))
            {
                bInJobs = FALSE;
                newi = i + strAffectedFilesEnglishLen;
            }
			else if (bInJobs && (*pAt > ' '))
			{
				newi = AddHotSpotWord(m_numHotSpots, i, beginChar, lgth, FALSE);
				m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAJOB);
                pAt += newi - i;
                i = newi;

                static const WCHAR strBy[] = L" by ";
                static const WCHAR strDate[] = L" on yyyy/mm/dd";
                static const int strByLen = STRLENW(strBy);
                static const int strDateLen = STRLENW(strDate);
				if (!wcsncmp(pAt + strDateLen, strBy, strByLen))
				{
					newi = AddHotSpotWord(m_numHotSpots, i + strDateLen + strByLen, beginChar, lgth, FALSE);
					m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISAUSER);
				}
			}
            else if(!wcsncmp(pAt, strFixes, strFixesLen))
            {
                bInFixes = TRUE;
                newi = i + strFixesLen;
                if(pAt[strFixesLen] == '\n')
                {
                    // if there are fixes, make sure we don't miss the newline
                    newi--;
                }
            }
			else if (bInFixes && !wcsncmp(pAt, strChange, strChangeLen))
			{
                newi = AddHotSpotWord(m_numHotSpots, i + strChangeLen, beginChar, lgth, FALSE);
				m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISACHG);
			}
			else if (!wcsncmp(pAt, strDifferences, strDifferencesLen))
			{
                bInDiffs = TRUE;
                newi = i + strDifferencesLen;
			}
            pAt += newi - i;
        }
		else if (!wcsncmp(pAt, strChangelist, strChangelistLen))
		{
            pAt += strChangelistLen + 1;
            i += strChangelistLen + 1;
            LPCWSTR pChar = pAt;
            while(iswdigit(*pChar))
                pChar++;

            if(pChar > pAt)
            {
			    i = AddHotSpotWord(m_numHotSpots, i, beginChar, pChar - pAt, FALSE);
                pAt = pChar;
			    m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISACHG);
            }
		}
		else if (m_bDiffOutput && !wcsncmp(pAt, strEqContent, strEqContentLen))
		{
			i = AddHotSpotWord(m_numHotSpots, i+5, beginChar, strEqContentLen-5, FALSE);
            pAt += strChangelistLen;
			m_HotSpotType.InsertAt(m_numHotSpots++, HS_ISDIFF2);
		}
        // if at end of line, note start of next line
        if(*pAt == '\n')
            beginChar = (pAt + 1) - m_DescriptionW;
	}
	if (m_numHotSpots == 0)
		return;

    CHARFORMAT cf;
    cf.cbSize = sizeof(cf);
    cf.dwMask = CFM_COLOR | CFM_UNDERLINE | CFM_LINK;
    cf.dwEffects = CFE_UNDERLINE|CFE_LINK;
	DWORD txtcolor = GetSysColor(COLOR_BTNTEXT);
	if (!txtcolor)
		cf.crTextColor = m_MoreThan256Colors ? RGB(0,0,0xFF) : RGB(0,128,0);
	else if (txtcolor == 0xFFFFFF)
		cf.crTextColor = RGB(0xFF,0,0xFF);
	else
	{
		if (GetBValue(txtcolor) + 0xFF > 0xFF)
			txtcolor ^= 0xFFFF00;
		cf.crTextColor = txtcolor;
	}

	long orgb, orge;
	m_Text.GetSel(orgb, orge);
	for (int i = -1; ++i < m_numHotSpots; )
	{
		int	b = m_HotSpotBgn.GetAt(i);
		int	e = m_HotSpotEnd.GetAt(i);
		m_Text.SetSel(b, e);
		m_Text.SetSelectionCharFormat(cf);
	}
	m_Text.SetSel(orgb, orge);
}

/*  _________________________________________________________________

	for commands that will run synchronously.
	_________________________________________________________________
*/

BOOL CSpecDescDlg::PumpMessages( )
{
	if (MainFrame()->IsQuitting())
		return FALSE;

	MSG msg;

	while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
	{
		//		get out if app is terminating
		//
		if ( msg.message == WM_QUIT )
			return FALSE;
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////
// CDialog doesn't support UI updating the way CFrameWnd does, so we have to
// handle WM_INITPOPOPMENU here.  See MSDN article Q242577.

void CSpecDescDlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu)
{
    ASSERT(pPopupMenu != NULL);
    // Check the enabled state of various menu items.

    CCmdUI state;
    state.m_pMenu = pPopupMenu;
    ASSERT(state.m_pOther == NULL);
    ASSERT(state.m_pParentMenu == NULL);

    // Determine if menu is popup in top-level menu and set m_pOther to
    // it if so (m_pParentMenu == NULL indicates that it is secondary popup).
    HMENU hParentMenu;
    if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)
        state.m_pParentMenu = pPopupMenu;    // Parent == child for tracking popup.
    else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)
    {
        CWnd* pParent = this;
           // Child windows don't have menus--need to go to the top!
        if (pParent != NULL &&
           (hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)
        {
           int nIndexMax = ::GetMenuItemCount(hParentMenu);
           for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
           {
            if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)
            {
                // When popup is found, m_pParentMenu is containing menu.
                state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
                break;
            }
           }
        }
    }

    state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
    for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
      state.m_nIndex++)
    {
        state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
        if (state.m_nID == 0)
           continue; // Menu separator or invalid cmd - ignore it.

        ASSERT(state.m_pOther == NULL);
        ASSERT(state.m_pMenu != NULL);
        if (state.m_nID == (UINT)-1)
        {
           // Possibly a popup menu, route to first item of that popup.
           state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
           if (state.m_pSubMenu == NULL ||
            (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
            state.m_nID == (UINT)-1)
           {
            continue;       // First item of popup can't be routed to.
           }
           state.DoUpdate(this, TRUE);   // Popups are never auto disabled.
        }
        else
        {
           // Normal menu item.
           // Auto enable/disable if frame window has m_bAutoMenuEnable
           // set and command is _not_ a system command.
           state.m_pSubMenu = NULL;
           state.DoUpdate(this, FALSE);
        }

        // Adjust for menu deletions and additions.
        UINT nCount = pPopupMenu->GetMenuItemCount();
        if (nCount < state.m_nIndexMax)
        {
           state.m_nIndex -= (state.m_nIndexMax - nCount);
           while (state.m_nIndex < nCount &&
            pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
           {
            state.m_nIndex++;
           }
        }
        state.m_nIndexMax = nCount;
    }
} 



BOOL CSpecDescDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{
    NMHDR* pNMHDR = (NMHDR*)lParam;
    if(pNMHDR->idFrom == IDC_DESCRIPTION && pNMHDR->code == EN_LINK)
    {
        ENLINK *link = (ENLINK*)lParam;
        if(link->msg == WM_LBUTTONUP)
        {
	        int i = IsItaHotSpot(link->chrg.cpMin, link->chrg.cpMax);
	        if (i != -1)
	        {
		        {
			        switch (m_HotSpotType.GetAt(i))
			        {
			        case HS_ISACHG:
                        m_Text.SetSel(link->chrg);
				        OnDescChg();
				        break;
			        case HS_ISAUSER:
                        m_Text.SetSel(link->chrg);
				        OnDescUser();
				        break;
			        case HS_ISACLIENT:
                        m_Text.SetSel(link->chrg);
				        OnDescClient();
				        break;
			        case HS_ISAJOB:
                        m_Text.SetSel(link->chrg);
				        OnDescJob();
				        break;
			        case HS_ISAEMAIL:
                        m_Text.SetSel(link->chrg);
				        OnEmail();
				        break;
			        case HS_ISAURL:
                        m_Text.SetSel(link->chrg);
				        OnURL();
				        break;
			        case HS_ISDIFF2:
                        m_Text.SetSel(link->chrg);
				        OnDiff2();
				        break;
			        }
		        }
	        }
            *pResult = 0;
            return TRUE;
        }
        else if(link->msg == WM_LBUTTONUP || link->msg == WM_RBUTTONUP)
        {
            m_Text.SetSel(link->chrg);
            *pResult = 0;
            return TRUE;
        }
    }
	
	return CDialog::OnNotify(wParam, lParam, pResult);
}


void CSpecDescDlg::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	CP4Menu popMenu;

	SetMenuFlags();

    if(point.x == -1 && point.y == -1)
    {
        GetCursorPos(&point);
    }
	if (pWnd == GetDlgItem(IDC_SHOWDIFFS))
	{
		CRect rect;
		pWnd->GetWindowRect(&rect);
		point.x = rect.left;
		point.y = rect.bottom + 1;
		popMenu.LoadMenu(IDR_SPECDESC_DIFF);
	}
	else
	{
	    popMenu.LoadMenu(IDR_SPECDESC_RICH);
		if (!MainFrame()->HaveP4QTree())
			popMenu.DeleteMenu(ID_FILE_REVISIONTREE, MF_BYCOMMAND);
		if (!AfxGetApp()->GetProfileInt(_T("Settings"), _T("CallTrack"), 0))
			popMenu.DeleteMenu(ID_CURRENTTASK,MF_BYCOMMAND);
		if (m_bDiffOutput && !(m_fDiff2 & (MF_GRAYED|MF_DISABLED)))
			popMenu.InsertMenu(IDB_BROWSE, MF_BYCOMMAND, ID_DIFF2, 
								LoadStringResource(IDS_DIFFTHE2FILES));
	}

	popMenu.GetSubMenu(0)->TrackPopupMenu( TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
	if (pWnd == GetDlgItem(IDC_SHOWDIFFS))
		m_btShowDiffs.ClearButtonPushed();
}

BOOL CSpecDescDlg::PreTranslateMessage(MSG* pMsg) 
{
	if (NULL != m_pToolTip)
            m_pToolTip->RelayEvent(pMsg);
	
	return CDialog::PreTranslateMessage(pMsg);
}

void CSpecDescDlg::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
	if (m_InitRect.Height())
	{
		lpMMI->ptMinTrackSize.x= m_InitRect.Width();
		lpMMI->ptMinTrackSize.y= m_InitRect.Height();
	}
}

// This signals the closing of a modeless dialog to
// MainFrame which will delete the 'this' object
void CSpecDescDlg::OnDestroy()
{
	if (m_Modeless)
		::PostMessage(MainFrame()->m_hWnd, WM_P4DLGDESTROY, 0, (LPARAM)this);
}

LRESULT CSpecDescDlg::OnNewClient(WPARAM wParam, LPARAM lParam)
{
	switch(m_viewType)
	{
	case P4CHANGE_SPEC:
	case P4CLIENT_SPEC:
		if (m_ShowEditBtn)
		{
			GetDlgItem(IDC_EDITIT)->EnableWindow(FALSE);
			GetDlgItem(IDC_EDITIT)->ShowWindow(SW_HIDE);
		}
		break;
	}
	return TRUE;
}

LRESULT CSpecDescDlg::OnNewUser(WPARAM wParam, LPARAM lParam)
{
	switch(m_viewType)
	{
	case P4CHANGE_SPEC:
	case P4USER_SPEC:
		if (m_ShowEditBtn)
		{
			GetDlgItem(IDC_EDITIT)->EnableWindow(FALSE);
			GetDlgItem(IDC_EDITIT)->ShowWindow(SW_HIDE);
		}
		break;
	}
	return TRUE;
}

void CSpecDescDlg::OnUpdatePositionToPattern(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( NULL == m_pFRDlg );
}

void CSpecDescDlg::OnPositionToPattern() 
{
	if (GetFocus()->m_hWnd != m_Text.m_hWnd)
	{
		MakeSmartSelection();
		CHARRANGE cr;
		m_Text.GetSel(cr);
		GotoDlgCtrl(GetDlgItem(IDC_DESCRIPTION));
		m_Text.SetSel(cr);
	}
	if ( NULL == m_pFRDlg )
	{
		m_pFRDlg = new CFindReplaceDialog();  // Must be created on the heap

		m_pFRDlg->m_fr.lStructSize = sizeof(FINDREPLACE);
		m_pFRDlg->m_fr.hwndOwner = this->m_hWnd;
		m_pFRDlg->Create( TRUE, m_FindWhatStr, _T(""), m_FindWhatFlags, this ); 
	}
}

void CSpecDescDlg::OnUpdatePositionToNext(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!m_FindWhatStr.IsEmpty());
}

void CSpecDescDlg::OnPositionToNext() 
{
	if (GetFocus()->m_hWnd != m_Text.m_hWnd)
	{
		MakeSmartSelection();
		CHARRANGE cr;
		m_Text.GetSel(cr);
		GotoDlgCtrl(GetDlgItem(IDC_DESCRIPTION));
		m_Text.SetSel(cr);
	}
	if (!m_FindWhatStr.IsEmpty())
	{
		PostMessage(WM_FINDPATTERN, (WPARAM)m_FindWhatFlags | 0x80000000, 
									(LPARAM)m_FindWhatStr.GetBuffer(0));
	}
}

void CSpecDescDlg::OnUpdatePositionToPrev(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!m_FindWhatStr.IsEmpty());
}

void CSpecDescDlg::OnPositionToPrev() 
{
	if (GetFocus()->m_hWnd != m_Text.m_hWnd)
	{
		MakeSmartSelection();
		CHARRANGE cr;
		m_Text.GetSel(cr);
		GotoDlgCtrl(GetDlgItem(IDC_DESCRIPTION));
		m_Text.SetSel(cr);
	}
	if (!m_FindWhatStr.IsEmpty())
	{
		PostMessage(WM_FINDPATTERN, ((WPARAM)m_FindWhatFlags | 0x80000000) ^ FR_DOWN, 
									 (LPARAM)m_FindWhatStr.GetBuffer(0));
	}
}

LONG CSpecDescDlg::OnFindReplace(WPARAM wParam, LPARAM lParam)
{
	LPFINDREPLACE lpfp = (LPFINDREPLACE)lParam;
	if (m_pFRDlg->FindNext() || m_pFRDlg->IsTerminating())
	{
		m_FindWhatStr = lpfp->lpstrFindWhat;
		m_FindWhatFlags = lpfp->Flags;
		if (m_pFRDlg->FindNext())
		{
			PostMessage(WM_FINDPATTERN, (WPARAM)(lpfp->Flags), 
										(LPARAM)m_FindWhatStr.GetBuffer(0));
			delete m_pFRDlg;
		}
		m_pFRDlg = NULL;
	}
	return 0;
}

LRESULT CSpecDescDlg::OnFindPattern(WPARAM wParam, LPARAM lParam)
{
	FINDTEXTEX ft;
	CHARRANGE cr;
	int flags = (int)wParam;
	CString what = (TCHAR *)lParam;

	m_Text.GetSel(cr);
	if (flags & FR_DOWN)
	{
		ft.chrg.cpMin = cr.cpMax;
		ft.chrg.cpMax = -1;
	}
	else
	{
		ft.chrg.cpMax = 0;
		ft.chrg.cpMin = cr.cpMin;
	}
	ft.lpstrText = what.GetBuffer();
	if (-1 != m_Text.FindText(flags, &ft))
		m_Text.SetSel(ft.chrgText);

	return 0;
}