SpecDescDlg.cpp. #1

  • //
  • guest/
  • YourUncleBob/
  • p4win/
  • main/
  • gui/
  • SpecDescDlg.cpp.
  • View
  • Commits
  • Open Download .zip Download (93 KB)
/*
 * 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;
}
# Change User Description Committed
#1 19924 YourUncleBob Populate -o //guest/perforce_software/p4win/...
//guest/YourUncleBob/p4win/.....
//guest/perforce_software/p4win/main/gui/SpecDescDlg.cpp
#1 16169 perforce_software Move files to follow new path scheme for branches.
//guest/perforce_software/p4win/gui/SpecDescDlg.cpp
#1 8562 Matt Attaway These feet never stop running.

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