Diff2Output.cpp. #1

  • //
  • guest/
  • YourUncleBob/
  • p4win/
  • main/
  • gui/
  • Diff2Output.cpp.
  • View
  • Commits
  • Open Download .zip Download (29 KB)
// CDiff2Output.cpp : implementation file
//

#include "stdafx.h"
#include "p4win.h"
#include "MainFrm.h"
#include "Diff2Output.h"
#include "StringUtil.h"
#include "RegKeyEx.h"
#include "FileInfoDlg.h"
#include "ViewerDlg.h"
#include "SpecDescDlg.h"
#include "cmd_diff2.h"
#include "cmd_fstat.h"
#include "cmd_history.h"
#include "cmd_opened.h"
#include "cmd_prepbrowse.h"
#include "cmd_where.h"
//#include "hlp\p4win.hh"

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


static LPCTSTR sRegKey = _T("Software\\Perforce\\P4Win\\Layout\\Diff2Output");
static LPCTSTR sRegValue_ColumnWidths = _T("Column Widths");
static LPCTSTR sRegValue_SortColumns = _T("Sort Columns");

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


/////////////////////////////////////////////////////////////////////////////
// CDiff2Output dialog


CDiff2Output::CDiff2Output(CWnd* pParent)
	: CDialog(CDiff2Output::IDD, pParent)
{
	//{{AFX_DATA_INIT(CDiff2Output)
	//}}AFX_DATA_INIT
	m_pParent = pParent;
	if (m_pParent)
		MainFrame()->SetModelessWnd(this);
	m_InitRect.SetRect(0,0,100,100);
	m_WinPos.SetWindow( this, _T("Diff2Output") );
}

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

void CDiff2Output::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CDiff2Output)
	DDX_Control(pDX, IDC_LIST, m_ListCtrl);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CDiff2Output, CDialog)
	//{{AFX_MSG_MAP(CDiff2Output)
	ON_WM_CONTEXTMENU()
	ON_WM_SIZE()
	ON_WM_GETMINMAXINFO()
	ON_NOTIFY(NM_DBLCLK, IDC_LIST, OnDblclickP4list)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, OnItemchangedP4List)
	ON_BN_CLICKED(ID_DIFF2, OnDiff2)
	ON_WM_CLOSE()
	ON_WM_DESTROY()
	ON_WM_SYSCOMMAND()
//	ON_BN_CLICKED(IDHELP, OnHelp)
//	ON_WM_HELPINFO()
	ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
	ON_COMMAND(ID_POSITIONDEPOT, OnPositionDepot)
	ON_COMMAND(IDB_BROWSE, OnFileAutobrowse)
	ON_COMMAND((UINT)(ID_FILE_PROPERTIES-30000), OnFileInformation)	// should be ID_FILE_PROPERTIES - but bug in M$ complier(!)
	ON_COMMAND((UINT)(ID_FILE_REVISIONTREE-30000), OnFileRevisionTree)
	ON_COMMAND(ID_FILE_ANNOTATE, OnFileAnnotate)
	ON_COMMAND(ID_FILE_REVISIONHISTORY, OnFileRevisionhistory)
	ON_COMMAND(ID_SINGLEPANEVIEW, OnSinglePaneView)
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_P4DIFF2, OnP4Diff2 )
	ON_MESSAGE(WM_P4PREPBROWSE, OnP4ViewFile )
	ON_MESSAGE(WM_P4FILEINFORMATION, OnP4FileInformation )
	ON_MESSAGE(WM_P4ENDFILEINFORMATION, OnP4EndFileInformation )
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDiff2Output message handlers

BOOL CDiff2Output::OnInitDialog() 
{
	int i;

	CDialog::OnInitDialog();
	// Record the initial window size, then see if there is a registry preference
	GetWindowRect(&m_InitRect);
	m_LastRect = m_InitRect;
	m_WinPos.RestoreWindowPosition();

	CString str;

	SetWindowText(m_caption);
	SetFont(m_Font);

	// Modify the list control style so that the entire selected row is highlighted
	DWORD dwStyle = ::SendMessage(m_ListCtrl.m_hWnd,LVM_GETEXTENDEDLISTVIEWSTYLE,0,0);
	dwStyle |= LVS_EX_FULLROWSELECT;
	::SendMessage(m_ListCtrl.m_hWnd,LVM_SETEXTENDEDLISTVIEWSTYLE,0,dwStyle);

	// Make sure list control shows selection when not the focused control
	m_ListCtrl.ModifyStyle(0, LVS_SHOWSELALWAYS, 0);

	// Get original size of control
	CRect rect;
	m_ListCtrl.GetWindowRect(&rect);

	int colwidth[2]={120,3000};

	// make sure OnSize gets called to reposition controls
	// if restored position is default, this won't happen unless
	// we force it
	GetClientRect(&rect);
	SendMessage(WM_SIZE, 0, MAKELONG(rect.Width(), rect.Height()));

	// Get new size of control after resized as specified in the registry
	m_ListCtrl.GetWindowRect(&rect);

	// Get any saved column widths from registry
	RestoreSavedWidths(colwidth, 2);

	// Make sure no column completely disappeared (because you can't get it back then)
	for (i=-1; ++i < 2; )
	{
		if (colwidth[i] < 5)
			colwidth[i] = 5;
	}
	// Use the same font as the calling window
	m_ListCtrl.SetFont(m_Font);

	// Figure out the headers
	int lgthHdr1;
	int lgthHdr2;
	CString txt;
	txt = m_ColNames->GetAt(0);
	if ((i = txt.Find(_T("/..."))) > -1)
		lgthHdr1 = i + 1;
	else
	{
		lgthHdr1 = 0;
	}
	txt = m_ColNames->GetAt(1);
	if ((i = txt.Find(_T("/..."))) > -1)
		lgthHdr2 = i + 1;
	else
	{
		lgthHdr2 = 0;
	}
	if (!lgthHdr1)	// local syntax?
	{
		txt = m_ColNames->GetAt(0);
		int i1 = txt.Find(_T("\\..."));
		if (i1 != -1)	// local syntax!
		{
			m_OrigHdr1 = txt.Left(i1);
			CCmd_Where *pCmd1 = new CCmd_Where;
			pCmd1->Init(NULL, RUN_SYNC);
			if ( pCmd1->Run(m_OrigHdr1) && !pCmd1->GetError() 
			  && pCmd1->GetDepotFiles()->GetCount() )
			{
				CString txt1 = pCmd1->GetDepotSyntax() + _T('/');
				lgthHdr1 = txt1.GetLength();
				txt1 += txt.Mid(i1+1);
				m_ColNames->SetAt(0, txt1);
			}
			else
				ASSERT(0);
			delete pCmd1;
			m_OrigHdr1 += _T('\\');
		}
	}
	if (!lgthHdr2)	// local syntax?
	{
		txt = m_ColNames->GetAt(1);
		int i2 = txt.Find(_T("\\..."));
		if (i2 != -1)	// local syntax!
		{
			m_OrigHdr2 = txt.Left(i2);
			CCmd_Where *pCmd1 = new CCmd_Where;
			pCmd1->Init(NULL, RUN_SYNC);
			if ( pCmd1->Run(m_OrigHdr2) && !pCmd1->GetError() 
			  && pCmd1->GetDepotFiles()->GetCount() )
			{
				CString txt2 = pCmd1->GetDepotSyntax() + _T('/');
				lgthHdr2 = txt2.GetLength();
				txt2 += txt.Mid(i2+1);
				m_ColNames->SetAt(1, txt2);
			}
			else
				ASSERT(0);
			delete pCmd1;
			m_OrigHdr2 += _T('\\');
		}
	}

	// these contain the folders including the last / but chop off the ... and following
	m_Hdr1 = m_ColNames->GetAt(0).Left(lgthHdr1);
	m_Hdr2 = m_ColNames->GetAt(1).Left(lgthHdr2);
	if (m_OrigHdr1.IsEmpty())
		m_OrigHdr1 = m_Hdr1;
	if (m_OrigHdr2.IsEmpty())
		m_OrigHdr2 = m_Hdr1;

	// Insert the columns 
	int maxcols = 2;
	int width=GetSystemMetrics(SM_CXVSCROLL);;
	int retval;
	LV_COLUMN lvCol;
	for(int subItem=0; subItem < maxcols; subItem++)
	{
		lvCol.mask= LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT |LVCF_WIDTH;
		lvCol.fmt=LVCFMT_LEFT;
		lvCol.pszText=( LPTSTR )( LPCTSTR ) m_ColNames->GetAt( subItem );
		lvCol.iSubItem=subItem;
		if(subItem < maxcols-1)
		{
			lvCol.cx=colwidth[subItem];
			width+=lvCol.cx;
		}
		else
			lvCol.cx=max(colwidth[subItem], rect.Width() - width - 4);  // expand last column to fill window
		retval=m_ListCtrl.InsertColumn(subItem, &lvCol);
	}
 
	AddTheListData(lgthHdr1, lgthHdr2);

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

void CDiff2Output::AddTheListData(int lgthHdr1, int lgthHdr2)
{
	// Add the data
	int i;
	LV_ITEM lvItem;
	LPARAM  lParam;
	int iActualItem = -1;

	int maxwcol2 = m_ListCtrl.GetStringWidth(m_ColNames->GetAt(1)) + 20;

	int iItem = 0;
	CString msg = m_Msg;
	while ((i = msg.Find(_T('\n'))) != -1)
	{
		CString line = msg.Left(i);
		line.TrimLeft(_T(" =\t\r\n"));
		msg = msg.Mid(i+1);

		if (line.GetAt(0) == _T('/'))
		{
			i = line.Find(_T('#'));
			if (i > 0)
				i = line.Find(_T(" - "), i);
		}
		else
			i = line.Find(_T(" - "));
		ASSERT(i != -1);

		CString col1txt = line.Left(i);
		CString col2txt = line.Mid(i+3);
		col2txt.TrimLeft();
		if (col2txt.GetAt(0) == _T('/'))
		{
			i = col2txt.Find(_T('#'));
			if (i > 0)
			{
				if ((i = col2txt.Find(_T(" content"), i)) != -1)
					col2txt = col2txt.Left(i);
			}
		}
		col2txt.TrimRight(_T(" =\t\r\n"));

		lParam = 1;							// 1 -> can diff 2 files
		if (col1txt.GetAt(0) == _T('/'))
		{
			if (col1txt.Find(m_Hdr1) == 0)
				col1txt = col1txt.Mid(lgthHdr1);
		}
		else
		{
			col1txt = _T("   ") + col1txt;
			lParam = 0;						// 0 -> cannot diff 2 files
		}
		if (col2txt.GetAt(0) == _T('/'))
		{
			if (col2txt.Find(m_Hdr2) == 0)
				col2txt = col2txt.Mid(lgthHdr2);
		}
		else
		{
			col2txt = _T("   ") + col2txt;
			lParam = 0;						// 0 -> cannot diff 2 files
		}

		maxwcol2 = max(maxwcol2, m_ListCtrl.GetStringWidth(col2txt)+20);

		for(int subItem=0; subItem < 2; subItem++)
		{
			lvItem.mask=LVIF_TEXT | 
					((subItem==0) ? LVIF_IMAGE : 0) |
					((subItem==0) ? LVIF_PARAM : 0);
			lvItem.iItem = (subItem==0) ? iItem : iActualItem;
            ASSERT(lvItem.iItem != -1);
			lvItem.iSubItem = subItem;
			lvItem.lParam = lParam;
			lvItem.pszText = const_cast<LPTSTR>( subItem==0 ? (LPCTSTR)col1txt : (LPCTSTR)col2txt);

			if(subItem==0)
				iActualItem=m_ListCtrl.InsertItem(&lvItem);
			else
				m_ListCtrl.SetItem(&lvItem);
		}
		iItem++;
	}

	int w = GetSystemMetrics(SM_CXVSCROLL);
	CRect rect;
	m_ListCtrl.GetWindowRect(&rect);
	m_ListCtrl.SetColumnWidth(1, max(maxwcol2 + w, 
									 rect.Width() - m_ListCtrl.GetColumnWidth(0) - 8 - w));

	// finally, set focus to the list control so that the first 'down'
	// keystroke can be used to scroll down
	m_ListCtrl.SetFocus();
}

void CDiff2Output::OnOK() 
{
	OnCancel();
}

void CDiff2Output::OnCancel() 
{
	m_WinPos.SaveWindowPosition();
	SaveColumnWidths();
	if (m_pParent)
		m_pParent->PostMessage(WM_P4ENDDIFF2OUTPUT, 0, (LPARAM)this);
	CDialog::OnCancel();
}

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

void CDiff2Output::OnSysCommand(UINT nID, LPARAM lParam) 
{
	switch(nID)
	{
	case SC_MINIMIZE:
		GetDesktopWindow()->ArrangeIconicWindows();
		break;
	}

	CDialog::OnSysCommand(nID, lParam);
}

void CDiff2Output::OnSize(UINT nType, int cx, int cy) 
{
	CDialog::OnSize(nType, cx, cy);
	
	// Slide the buttons to the bottom of dlg
	CWnd *pOldV = GetDlgItem(ID_SINGLEPANEVIEW);
	CWnd *pDiff = GetDlgItem(ID_DIFF2);
	CWnd *pCancel = GetDlgItem(IDCANCEL);

	if(!pCancel)
		return;

	int h, y;
	CRect rect;
	GetWindowRect(&rect);
	int dx = rect.Width() - m_LastRect.Width();
	int dy = rect.Height() - m_LastRect.Height();
	// Save the new size
	m_LastRect = rect;

	pOldV->GetWindowRect(&rect);
	ScreenToClient(rect);
	pOldV->SetWindowPos(NULL, rect.left, rect.top + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

	pDiff->GetWindowRect(&rect);
	ScreenToClient(rect);
	pDiff->SetWindowPos(NULL, rect.left + dx, rect.top + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

	pCancel->GetWindowRect(&rect);
	ScreenToClient(rect);
	h = rect.Height();
	pCancel->SetWindowPos(NULL, rect.left + dx, y = rect.top + dy, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

	// Increase the size of the list both horiz and vert
	CWnd *pList = GetDlgItem(IDC_LIST);
	pList->GetWindowRect(&rect);
	pList->SetWindowPos(NULL, 0, 0, rect.right - rect.left + dx, 
								    y - h, SWP_NOMOVE | SWP_NOZORDER);
	RedrawWindow();
}

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


// Check the registry to see if we have recorded the
// column widths lastused for the Diff2Output dialog
void CDiff2Output::RestoreSavedWidths(int *width, int numcols)
{
    CRegKeyEx key;

    CString theKey = CString(sRegKey);
    if(ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, theKey, KEY_READ))
    {
        CString result = key.QueryValueString(sRegValue_ColumnWidths);
		CString sortCols = key.QueryValueString(sRegValue_SortColumns);
        if(!result.IsEmpty())
        {
			//		things can go wrong with the registry setting of the 
			//		widths. Use the defaults if the entry is all zeroes.
			//
			if ( result != _T("0,0,0,0,0,0,0,0,0,0") )
				for(int i=0; i< numcols; i++)
					width[i]= GetPositiveNumber(result);
        }
    }
}

void CDiff2Output::SaveColumnWidths() 
{
	// Save the column widths
	CString str;

	for(int i=0; i < 10; i++)
	{
		// Note that GetColumnWidth returns zero if i > numcols
    	CString num;
		num.Format(_T("%d"), m_ListCtrl.GetColumnWidth(i));
		if(i)
			str+=_T(",");
		str+=num;
	}
	
    CRegKeyEx key;
    if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, sRegKey))
        key.SetValueString(str, sRegValue_ColumnWidths);
}

#ifdef	HELPWANTEDFORCDiff2Output
void CDiff2Output::OnHelp() 
{
	AfxGetApp()->WinHelp(0);
}

BOOL CDiff2Output::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	OnHelp();
	return TRUE;
}
#endif

/*  _________________________________________________________________

	for commands that will run synchronously.
	_________________________________________________________________
*/

BOOL CDiff2Output::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;
}

/*  _________________________________________________________________

	Parse //depot/folder/filename#rev (type) from a column into its parts
	_________________________________________________________________
*/
CString CDiff2Output::ParseFileInfo(CString *itemStr, int *rev, CString *filetype)
{
	CString filename = *itemStr;
	int i;
	if ((i = filename.ReverseFind(_T('('))) != -1)
	{
		if (filetype)
		{
			*filetype = filename.Mid(i);
			filetype->TrimLeft(_T('('));
			filetype->TrimRight(_T(')'));
		}
		filename = filename.Left(i-1);
	}

	if ((i = filename.Find(_T('#'))) != -1)
	{
		if (rev)
		{
			*rev = _ttoi(filename.Right(filename.GetLength() - i - 1));
			if (!*rev)
				*rev = -1;
		}
		filename = filename.Left( i );  // trim off rev# info
	}
	return filename;
}

void CDiff2Output::OnItemchangedP4List(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;

	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);
	CWnd *ctl = GetDlgItem(ID_DIFF2);
	ctl->UpdateWindow();
	BOOL enable = m_ListCtrl.GetItemData(index);
	if(enable != ctl->IsWindowEnabled())
		ctl->EnableWindow(enable);	// 0 -> cannot diff; 1 -> can diff
}


void CDiff2Output::OnDblclickP4list(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;
	OnDiff2();
}

void CDiff2Output::OnDiff2()
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);
	if (!m_ListCtrl.GetItemData(index))	// 0 -> cannot diff; 1 -> can diff
	{
		MessageBeep(0);
		return;
	}

	CString col1txt = m_ListCtrl.GetItemText(index, 0);
	CString col2txt = m_ListCtrl.GetItemText(index, 1);
	if (col1txt.GetAt(0) != _T('/'))
		col1txt = m_Hdr1 + col1txt;
	if (col2txt.GetAt(0) != _T('/'))
		col2txt = m_Hdr2 + col2txt;

	int rev1 = -1;
	int rev2 = -1;

	CString filetype1 = _T("text");
	CString filetype2 = _T("text");

	col1txt = ParseFileInfo(&col1txt, &rev1, &filetype1);
	col2txt = ParseFileInfo(&col2txt, &rev2, &filetype2);

	CCmd_Diff2 *pCmd= new CCmd_Diff2;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK_IF_HAVE_KEY, m_Key);
	if( pCmd->Run( col1txt, col2txt, rev1, rev2, filetype1, filetype2) )
	{
		UPDATE_STATUS( LoadStringResource(IDS_DIFFING_FILES) );
	}
	else
		delete pCmd;
}

LRESULT CDiff2Output::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 CDiff2Output::OnContextMenu(CWnd* pWnd, CPoint point)
{
	if (SERVER_BUSY())
		return;

	// create an empty context menu
	CMenu popMenu;
	popMenu.CreatePopupMenu();

	CString itemStr;
	RECT rect;
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
	{
		m_ListCtrl.GetWindowRect(&rect);
		if (point.x < rect.left)
		{
			point.x = rect.left + 8;
			point.y = rect.top + 32;
		}
		goto singlepaneview;
	}
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	// make a new selection new if reqd
	if(m_ListCtrl.GetItemState(index,LVIS_SELECTED) != LVIS_SELECTED)
	{
		for(int i=m_ListCtrl.GetItemCount(); i>=0; i-- )
			m_ListCtrl.SetItemState(i, 0, LVIS_SELECTED);
		
		m_ListCtrl.SetItemState(index, LVIS_SELECTED, LVIS_SELECTED);
	}

	LPARAM b;
	if ((b = m_ListCtrl.GetItemData(index)) > 0)	// 0 -> cannot diff; 1 -> can diff
	{
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_DIFF2, LoadStringResource(IDS_DIFFTHE2FILES));
		popMenu.AppendMenu(MF_SEPARATOR);
	}

	// convert the screen coords in point to m_ListCtrl coords
	POINT clipoint;
	rect.left = rect.top = 0;
	rect.right = point.x;
	rect.bottom = point.y;
	m_ListCtrl.ScreenToClient(&rect);
	clipoint.x = rect.right;
	clipoint.y = rect.bottom;

	// find which column was clicked
	LVHITTESTINFO lvhti;
	lvhti.pt = clipoint;
	int i = m_ListCtrl.SubItemHitTest(&lvhti);
	m_SubItem = (i == index) ? lvhti.iSubItem : 0;

	if (i != index)
	{
		POINT pt;
		m_ListCtrl.GetItemPosition(index, &pt);
		m_ListCtrl.GetWindowRect(&rect);
		point.x = rect.left + pt.x + 8;
		point.y = rect.top + pt.y + 8;
	}

	itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (!b && (itemStr.Find(_T('<')) != -1))
		goto singlepaneview;

	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_EDIT_COPY, LoadStringResource(IDS_EDIT_COPY));
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_POSITIONDEPOT, LoadStringResource(IDS_POSITIONDEPOT) );
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, IDB_BROWSE, LoadStringResource(IDS_VIEWUSING3DOTS));
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_FILE_PROPERTIES-30000, LoadStringResource(IDS_PROPERTIES));	// should be ID_FILE_PROPERTIES - but bug in M$ complier(!)
	if (MainFrame()->HaveP4QTree())
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_FILE_REVISIONTREE-30000,LoadStringResource(IDS_REVISIONTREE));
	if (MainFrame()->HaveTLV())
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_FILE_ANNOTATE, LoadStringResource(IDS_ANNOTATIONS));
 	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_FILE_REVISIONHISTORY, LoadStringResource(IDS_REVISIONHISTORY));
	popMenu.AppendMenu(MF_SEPARATOR);

singlepaneview:
 	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_SINGLEPANEVIEW, LoadStringResource(IDS_SINGLEPANEVIEW));

	// Finally blast the menu onto the screen
	if (popMenu.GetMenuItemCount() > 0)
		popMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,	point.x, point.y, this);
}

void CDiff2Output::OnEditCopy() 
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	int rev;
	itemStr = ParseFileInfo(&itemStr, &rev);
	CString selText;
	selText.Format(_T("%s#%d"), itemStr, rev);
    CopyTextToClipboard(selText);
}

void CDiff2Output::OnPositionDepot()
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	itemStr = ParseFileInfo(&itemStr);
	((CMainFrame *) AfxGetMainWnd())->ExpandDepotString( itemStr, TRUE );
}

void CDiff2Output::OnFileAutobrowse() 
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString name = m_ListCtrl.GetItemText(index, m_SubItem);
	if (name.GetAt(0) != _T('/'))
		name = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + name;
	int rev = -1;
	CString fileType = _T("text");
	name = ParseFileInfo(&name, &rev, &fileType);

	// 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;
}

// 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 CDiff2Output::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 CDiff2Output::OnFileInformation() 
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	itemStr = ParseFileInfo(&itemStr);

	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 CDiff2Output::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, 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 CDiff2Output::OnP4EndFileInformation( WPARAM wParam, LPARAM lParam )
{
	CFileInfoDlg *dlg = (CFileInfoDlg *)lParam;
	dlg->DestroyWindow();
	return TRUE;
}

void CDiff2Output::OnFileRevisionTree() 
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	itemStr = ParseFileInfo(&itemStr);
	TheApp()->CallP4RevisionTree(itemStr);	// use p4v.exe for tree
}

void CDiff2Output::OnFileAnnotate() 
{
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	itemStr = ParseFileInfo(&itemStr);
	TheApp()->CallP4A(itemStr, _T(""), 0);	// use p4v.exe for annotate
}

void CDiff2Output::OnFileRevisionhistory() 
{
	int rev = -1;
	POSITION pos = m_ListCtrl.GetFirstSelectedItemPosition();
	if (!pos)
		return;
	int index = m_ListCtrl.GetNextSelectedItem(pos);

	CString itemStr = m_ListCtrl.GetItemText(index, m_SubItem);
	if (itemStr.GetAt(0) != _T('/'))
		itemStr = CString(m_SubItem == 0 ? m_Hdr1 : m_Hdr2) + itemStr;
	itemStr = ParseFileInfo(&itemStr, &rev);
	
	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 CDiff2Output::OnSinglePaneView()
{
	// Do this first to trigger a repaint of the buttons
	// and before the new dialog is created (so it gets the focus)
	GotoDlgCtrl(GetDlgItem(IDC_LIST));

	CSpecDescDlg *dlg = new CSpecDescDlg(m_pParent);
	dlg->SetIsModeless(TRUE);
	dlg->SetKey(m_Key);
	dlg->SetDescription( m_Msg );
	dlg->SetItemName( m_OrigHdr1 + _T("... <> ") + m_OrigHdr2 + _T("...") );
	dlg->SetCaption( m_caption );
	dlg->SetViewType(P4DESCRIBE);
	if (!dlg->Create(IDD_SPECDESC, m_pParent))	// display the description dialog box
	{
		dlg->DestroyWindow();	// some error! clean up
		delete dlg;
	}
}
# Change User Description Committed
#1 19924 YourUncleBob Populate -o //guest/perforce_software/p4win/...
//guest/YourUncleBob/p4win/.....
//guest/perforce_software/p4win/main/gui/Diff2Output.cpp
#1 16169 perforce_software Move files to follow new path scheme for branches.
//guest/perforce_software/p4win/gui/Diff2Output.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.