//
// Copyright 1997 Nicholas J. Irias.  All rights reserved.
//
//

// JobListCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "p4win.h"
#include "JobListCtrl.h"
#include "JobsConfigure.h"
#include "MainFrm.h"
#include "TokenString.h"
#include "JobDescribe.h"
#include "SpecDescDlg.h"
#include "cmd_editspec.h"
#include "cmd_jobs.h"
#include "cmd_delete.h"
#include "catchalldlg.h"
#include "RegKeyEx.h"
#include "ImageList.h"

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

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

#define IMG_INDEX(x) (x-IDB_PERFORCE)

/////////////////////////////////////////////////////////////////////////////
// CJobListCtrl

IMPLEMENT_DYNCREATE(CJobListCtrl, CP4ListCtrl)

BEGIN_MESSAGE_MAP(CJobListCtrl, CP4ListCtrl)
	ON_WM_CREATE()
	ON_UPDATE_COMMAND_UI(ID_VIEW_UPDATE_RIGHT, OnUpdateViewUpdate)
	ON_WM_CONTEXTMENU()
	ON_UPDATE_COMMAND_UI(ID_JOB_DELETE, OnUpdateJobDelete)
	ON_COMMAND(ID_JOB_DELETE, OnJobDelete)
	ON_UPDATE_COMMAND_UI(ID_JOB_EDITSPEC, OnUpdateJobEditspec)
	ON_COMMAND(ID_JOB_EDITSPEC, OnJobEditspec)
	ON_NOTIFY_REFLECT(LVN_DELETEITEM, OnDeleteitem)
	ON_UPDATE_COMMAND_UI(ID_JOB_DESCRIBE, OnUpdateJobDescribe)
	ON_WM_LBUTTONDBLCLK()

	ON_COMMAND(ID_SETFILTER_JOBS, OnJobSetFilter)
	ON_COMMAND(ID_CLEARFILTER_JOBS, OnJobRemovefilter)

	ON_UPDATE_COMMAND_UI(ID_JOB_SETFILTER, OnUpdateJobSetFilter)
	ON_COMMAND(ID_JOB_SETFILTER, OnJobSetFilter)
	ON_UPDATE_COMMAND_UI(ID_JOB_REMOVEFILTER, OnUpdateJobRemovefilter)
	ON_COMMAND(ID_JOB_REMOVEFILTER, OnJobRemovefilter)

	ON_UPDATE_COMMAND_UI(ID_JOB_SETFILEFILTER, OnUpdateJobSetFileFilter)
	ON_COMMAND(ID_JOB_SETFILEFILTER, OnJobSetFileFilter)
	ON_UPDATE_COMMAND_UI(ID_JOB_SETFILEFILTERINTEG, OnUpdateJobSetFileFilter)
	ON_COMMAND(ID_JOB_SETFILEFILTERINTEG, OnJobSetFileFilterInteg)
	ON_UPDATE_COMMAND_UI(ID_JOB_REMOVEFILEFILTER, OnUpdateJobRemoveFileFilter)
	ON_COMMAND(ID_JOB_REMOVEFILEFILTER, OnJobRemoveFileFilter)

	ON_UPDATE_COMMAND_UI(ID_JOB_NEW, OnUpdateJobNew)
	ON_COMMAND(ID_JOB_NEW, OnJobNew)
	ON_COMMAND(ID_JOB_DESCRIBE, OnDescribe)
	ON_COMMAND(ID_VIEW_UPDATE_RIGHT, OnViewUpdate)
	ON_UPDATE_COMMAND_UI(ID_JOB_CONFIGURE, OnUpdateJobConfigure)
	ON_COMMAND(ID_JOB_CONFIGURE, OnJobConfigure)
	ON_COMMAND(ID_PERFORCE_OPTIONS, OnPerforceOptions)
	ON_MESSAGE(WM_P4JOBS, OnP4JobList )
	ON_MESSAGE(WM_P4EDITSPEC, OnP4JobSpec )
	ON_MESSAGE(WM_P4ENDSPECEDIT, OnP4EndSpecEdit )
	ON_MESSAGE(WM_P4DELETE, OnP4Delete )
	ON_MESSAGE(WM_P4JOBSPEC, OnP4JobSpecColumnNames )
	ON_MESSAGE(WM_P4DESCRIBE, OnP4Describe )
    ON_MESSAGE(WM_P4ENDDESCRIBE, OnP4EndDescribe )
    ON_MESSAGE(WM_FETCHJOBS, OnFetchJobs )
    ON_MESSAGE(WM_QUERYJOBS, OnQueryJobs )
    ON_MESSAGE(WM_QUERYJOBSPEC, OnQueryJobSpec )
    ON_MESSAGE(WM_QUERYJOBFIELDS, OnQueryJobFields )
    ON_MESSAGE(WM_QUERYJOBCOLS, OnQueryJobColumns )
    ON_MESSAGE(	WM_QUERYJOBSELECTION, OnQueryJobSelection )
	ON_MESSAGE(WM_JOB_FILTER, OnJobFilter2)
	ON_MESSAGE(WM_CLEARLIST, OnClear)
END_MESSAGE_MAP()

CJobListCtrl::CJobListCtrl()
{
	m_SortAscending = m_FilterIncIntegs = FALSE;
	m_LastSortCol=0;
	m_viewType = P4JOB_SPEC;
	m_bAlreadyGotColumns = m_Need2CallOnJobConfigure = FALSE;
    m_PostListToChangeNum= 0;
	m_PostListToChangeWnd= 0;
	m_Need2DoNew = FALSE;
	m_NewJob = FALSE;
    m_ColCodes.RemoveAll();
    m_captionplain = LoadStringResource(IDS_PERFORCE_JOBS);

	m_FastJobs = GetSavedColumnNames(m_DesiredCols, _T("Job List"));
	m_CF_JOB   = static_cast<CLIPFORMAT>(RegisterClipboardFormat(LoadStringResource(IDS_DRAGFROMJOB)));
	m_CF_DEPOT = static_cast<CLIPFORMAT>(RegisterClipboardFormat(LoadStringResource(IDS_DRAGFROMDEPOT)));
}

CJobListCtrl::~CJobListCtrl()
{

}

/////////////////////////////////////////////////////////////////////////////
// CJobListCtrl diagnostics

#ifdef _DEBUG
void CJobListCtrl::AssertValid() const
{
	CP4ListCtrl::AssertValid();
}

void CJobListCtrl::Dump(CDumpContext& dc) const
{
	CP4ListCtrl::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CJobListCtrl message handlers

void CJobListCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	int index = GetHitItem ( point );
	if(index != -1)
	{
		SetItemState( index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED );
		OnDescribe();
	}
	else
		CP4ListCtrl::OnLButtonDblClk(nFlags, point);
}

LRESULT CJobListCtrl::OnClear( WPARAM wParam, LPARAM lParam )
{
	m_sFilter.Empty( );
	PersistentJobFilter( KEY_READ );
	Clear();
	return 0;
}

void CJobListCtrl::Clear()
{
    SetRedraw(FALSE);
	DeleteAllItems();
    SetRedraw(TRUE);

	CP4ListCtrl::Clear();
}

/////////////////////////////////
// Three functions to allow the changelist pane to fetch the job list
// and the current job codes list
/////////////////////////////////

LRESULT CJobListCtrl::OnFetchJobs( WPARAM wParam, LPARAM lParam )
{
    m_PostListToChangeNum= wParam;
	m_PostListToChangeWnd= (HWND)lParam;

    if( GetItemCount() > 0 )
    {
        ::SendMessage(m_PostListToChangeWnd, WM_P4JOBS, m_PostListToChangeNum, 0);
        m_PostListToChangeNum=0;
		m_PostListToChangeWnd=0;
    }
    else
        OnViewUpdate();

    return 0;
}

LRESULT CJobListCtrl::OnQueryJobs( WPARAM wParam, LPARAM lParam )
{
    CObList *m_pJobList= new CObList;

    // Make a copy of the joblist, so the consumer doesnt need
    // to worry about any refresh that happens here at a later
    // time.  (They just need to delete the list when done)
    for( int i = 0; i < GetItemCount(); i++ )
	{
        CP4Job *job= (CP4Job *) GetItemData(i);
		CP4Job *newJob= new CP4Job;
        newJob->Create(job);
        m_pJobList->AddHead(newJob);
	}  

    return (LRESULT) m_pJobList;
}

LRESULT CJobListCtrl::OnQueryJobSpec( WPARAM wParam, LPARAM lParam )
{
	return (LRESULT)&m_Spec;
}

LRESULT CJobListCtrl::OnQueryJobFields( WPARAM wParam, LPARAM lParam )
{
    // The caller has provided an array.  Just transpose our array
    // into that array, to give the caller a snapshot.
    CArray<int,int> *array= ( CArray<int,int> *) wParam;

    array->RemoveAll();
    for(int i=0; i < m_ColCodes.GetSize(); i++)
        array->Add( m_ColCodes[i] );

    return (LRESULT) array;
}

LRESULT CJobListCtrl::OnQueryJobColumns( WPARAM wParam, LPARAM lParam )
{
	return (LRESULT)&m_ColNames;
}

LRESULT CJobListCtrl::OnQueryJobSelection( WPARAM wParam, LPARAM lParam )
{
	m_Active = GetSelectedItemText();
	return (LRESULT)&m_Active;
}

void CJobListCtrl::OnUpdateJobNew(CCmdUI* pCmdUI) 
{
	pCmdUI->SetText ( LoadStringResource( IDS_NEW ) );
	pCmdUI->Enable(!SERVER_BUSY() && !m_EditInProgress);		
	m_Need2DoNew = FALSE;
}

/*
	_________________________________________________________________

	since users can now define their own types in the job tracker, we
	have to get the names from the server. 
	_________________________________________________________________
*/

LRESULT CJobListCtrl::OnP4JobSpecColumnNames( WPARAM wParam, LPARAM lParam )
{
	CCmd_JobSpec *pCmd = ( CCmd_JobSpec *) wParam;
	ASSERT_KINDOF(CCmd_JobSpec, pCmd);

	if(!pCmd->GetError())
	{
        // Delete the old column names
		int i;
        for( i= m_ColCodes.GetUpperBound(); i>=0; i-- )
            DeleteColumn(i);

		pCmd->GetSpec( m_Spec );
		m_FieldNames.SetSize(0);
		GetFldNames( m_FieldNames, m_Spec );

		//	Default column widths based on all 5 default columns being present
		//
		int width[MAX_JOBS_COLUMNS] = { 90,80,60,90,200 };

		RestoreSavedWidths( width, max(MAX_JOBS_COLUMNS, m_ColCodes.GetSize()), _T("Job List") );
		for (i = -1; ++i < MAX_JOBS_COLUMNS; )
		{
			if (width[i] > 5000)
				width[i] = 30;
		}
		InsertColumnHeaders( m_ColNames, width );
		m_bAlreadyGotColumns = TRUE;

		//	okay, we got all the job column names.
		//	now get the job list or start the configure dialog
		//
		if (m_Need2CallOnJobConfigure)
			OnJobConfigure();
		else
			GetJobs( );
	}
    else if(m_PostListToChangeNum != 0)
    {
        ::SendMessage(m_PostListToChangeWnd, WM_P4JOBS, m_PostListToChangeNum, 0);
        m_PostListToChangeNum=0;
		m_PostListToChangeWnd=0;
    }
        
	
	delete pCmd;
	MainFrame()->ClearStatus();
	return 0;
}


/*
	_________________________________________________________________

	Simple parse code to check jobspec for spec codes 101-105, 
    which are reserved codes for default fields that may be present in the
    output of p4 jobs.  Record the column headings for these codes.
	_________________________________________________________________
*/

BOOL CJobListCtrl::GetFldNames( CStringArray &fldNames, const CString &spec )
{
	int i;
    CString errorText;
    CString fields;

	m_SpecNames.RemoveAll();
	m_ColNames.RemoveAll();
	m_ColNames.SetSize(0);
	m_ColNames.Copy(m_DesiredCols);

	//  Get the field names from the spec. they are between the word 
    //  "Fields:" and the word "Required" strip the separator.  Don't
    //  crash if the jobspec is not recognized.
    
	int start= spec.Find(_T("Fields:")) + lstrlen(_T("Fields:"));
    int end= spec.Find(_T("Required:"));
    if( start == -1 || end == -1 || end < start || end >= spec.GetLength())
        errorText=LoadStringResource(IDS_UNABLE_TO_FIND_FIELD_DELIMITERS);
    
    if( errorText.IsEmpty() )
    {
	    fields = spec.Mid(start, end - start);
	    start= fields.Find( 0x09 );
        if( start == -1 )
            errorText=LoadStringResource(IDS_UNABLE_TO_FIND_INITIAL_FIELD_DELIMITER);
    }

    if( errorText.IsEmpty() )
    {
        m_ColCodes.RemoveAll();        
	    fields = fields.Mid( start + 1 );

	    // Pick out the requested field names if present
	    
	    int code;
	    CString field;
		int max = m_ColNames.GetSize();
    	do  
	    {
		    //		get the field spec (e.g., "101 Job word 32")
		    //		and truncate the fields.
		    //
			int delim = fields.Find( 0x0a);
			if(delim < 9)	// need at least "n Job x n" or something's wrong
				break;
		    field = fields.Left ( delim + 1 );
		    fields = fields.Right ( fields.GetLength( ) - (delim + 1) );

		    //		get the "101" part of the spec in code
		    //
			delim = field.Find(_T(' '));
			if(delim < 1)
				break;
		    code = _ttoi ( field.Left ( delim ) ) ;
		    field = field.Right ( field.GetLength ( ) - (delim + 1) );
		    //		get the "Job" part in field.
			//
			delim = field.Find(_T(' '));
			if(delim < 1)
				break;
		    field = field.Left ( delim );

			m_SpecNames.Add(field);	// save all field names from the spec here.

			if (code == 101)	// if this is the jobname field, capture its name for 1st column
				m_ColNames.SetAt(0, field);
			else if (code == 103)
				m_ReportedByTitle = field;

			for (i = -1; ++i < max; )
            {
				CString colname = m_ColNames.GetAt(i);
				if (colname == field)
				{
					fldNames.Add( field );
	                m_ColCodes.Add( code );
					break;
				}
				else if ((colname.GetAt(0) == _T(':')) && (colname.GetAt(1) == _T('1')) && (colname.GetAt(2) == _T('0')))
				{
					TCHAR digit = colname.GetAt(3);
					int colcode = 100 + (digit & 0x0F);
					if (colcode == code)
					{
						m_ColNames.SetAt(i, field);
						fldNames.Add( field );
						m_ColCodes.Add( code );
						break;
					}
				}
            }
	    }
	    while ( !field.IsEmpty( ) );
    }

    if( !errorText.IsEmpty() )
    {
        // Very unlikely that we will ever report this error, but an error
        // message is always preferable to an inexplicable crash
		AddToStatus(CString(_T("JobView::GetFldNames()") + errorText), SV_ERROR);
        return FALSE;
    }
    else
	    return TRUE;
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::InsertJob(CP4Job *job, int index)
{
	LV_ITEM lvItem;
	int iActualItem = -1;
	CString txt;
	CString colName;
	
	ASSERT(job != NULL);
	m_iImage = CP4ViewImageList::VI_JOB;

	int maxcols = m_ColNames.GetSize();
	for(int subItem=0; subItem < maxcols; subItem++)
	{
		lvItem.mask=LVIF_TEXT | 
					((subItem==0) ? LVIF_IMAGE : 0) |
					((subItem==0) ? LVIF_PARAM : 0);

		lvItem.iItem= (subItem==0) ? index : iActualItem;
        ASSERT(lvItem.iItem != -1);
		lvItem.iSubItem= subItem;
		lvItem.iImage = CP4ViewImageList::VI_JOB;
		lvItem.lParam=(LPARAM) job;

		txt=PadCRs(job->GetJobField(subItem));
		lvItem.pszText = const_cast<LPTSTR>( (LPCTSTR ) txt);

		if(subItem==0)
	    	iActualItem=InsertItem(&lvItem);
		else
		    SetItem(&lvItem);
        
	}
}


/*	_________________________________________________________________
	
	After a spec edit, update the appropriate tree item
	_________________________________________________________________
*/

void CJobListCtrl::UpdateJob(CP4Job *job, int index)
{
	// First, switch the user data
	CP4Job *oldJob= (CP4Job *) GetItemData(index);
	delete oldJob;
	SetItemData(index, (LPARAM) job);

    CString txt;
	CString colName;

    // Then update the text for any fields that are present
	int maxCols = m_ColNames.GetSize();
    for( int subItem=0; subItem < maxCols; subItem++ )
    {
		txt=PadCRs(job->GetJobField(subItem));
		SetItemText(index, subItem, const_cast<LPTSTR>((LPCTSTR)txt));
    }
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::OnUpdateJobDescribe(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( OnUpdateShowMenuItem( pCmdUI, IDS_DESCRIBEIT_s ) );	
}


/*
	_________________________________________________________________
*/

LRESULT CJobListCtrl::OnP4JobList(WPARAM wParam, LPARAM lParam)
{
	POSITION pos;
	CP4Job *job;
	int index;
	CObList *jobs;

	CCmd_Jobs *pCmd= (CCmd_Jobs *) wParam;

	if(!pCmd->GetError())
	{
		SET_BUSYCURSOR();
		jobs= pCmd->GetList();

        SetRedraw(FALSE);
		CString bigjob = _T("");
		for(pos= jobs->GetHeadPosition(), index=0; pos != NULL; index++)
		{
			job=(CP4Job *) jobs->GetNext(pos);
			if (m_FilterView.GetCount() > 1)	// check for duplicates
			{									// if filtering on more than 1 file
				CString jobname = job->GetJobName();
				if (jobname <= bigjob)
				{
					if (jobname == bigjob || FindInList(jobname) != -1)
					{
						delete job;
						continue;
					}
				}
				else
					bigjob = jobname;
			}
			InsertJob(job, index);
		}
        SetRedraw(TRUE);

		CString msg;
		msg.FormatMessage(IDS_NUMBER_OF_JOBS_n, index );
		AddToStatus( msg, SV_COMPLETION );

		////jobs->RemoveAll();

		ReSort();
	
		if(jobs->GetCount() > 0)
		{
			int i = FindInList(m_Active);
			if (i < 0)	i=0;
			SetItemState(i, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
			EnsureVisible(i, FALSE);
		}

		CP4ListCtrl::SetUpdateDone();
		if (m_Need2DoNew)
			OnJobNew();
		::SetCursor(::LoadCursor(NULL, IDC_ARROW));
	}
	else
	{
		CP4ListCtrl::SetUpdateFailed();
		m_Need2DoNew = FALSE;
	}

    if(m_PostListToChangeNum != 0)
    {
        ::SendMessage(m_PostListToChangeWnd, WM_P4JOBS, m_PostListToChangeNum, 0);
        m_PostListToChangeNum= 0;
		m_PostListToChangeWnd= 0;
    }
	
    delete pCmd;
	MainFrame()->ClearStatus();

	// Notify the mainframe that we have finished getting the jobs,
	// hence the entire set of async command have finished.
	MainFrame()->ExpandDepotIfNeedBe();

	return 0;
}

/*
	_________________________________________________________________
*/

void CJobListCtrl::OnUpdateViewUpdate(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!SERVER_BUSY() && !MainFrame()->IsModlessUp());
}

void CJobListCtrl::OnViewUpdate( ) 
{ 
	MainFrame()->SetJobUpdateTime(GetTickCount());
	m_Active = GetSelectedItemText();
	SetCaption( );
	if( m_ReadSavedWidths )
		SaveColumnWidths();


	if( GET_SERVERLEVEL() > 3 )
		GetJobSpec( ) ;		//which also calls GetJobs( )
    else
    {
        // Against 97.3 server, the column names are always the same
       	m_ColNames.RemoveAll();
        m_ColNames.Add(LoadStringResource(IDS_P4JOB));
        m_ColNames.Add(LoadStringResource(IDS_P4STATUS));
        m_ColNames.Add(LoadStringResource(IDS_REPORTEDBY));
        m_ColNames.Add(LoadStringResource(IDS_REPORTEDDATE));
        m_ColNames.Add(LoadStringResource(IDS_DESCRIPTION));

        m_ColCodes.RemoveAll();

		int i;
        for(i=101; i <= 105; i++)
            m_ColCodes.Add(i);
        

		//		there is no default for the jobs now that the fields 
		//		are user-defined.
		//
		int width[ MAX_JOBS_COLUMNS ] = { 90,80,60,90,200 };

		RestoreSavedWidths( width, m_ColCodes.GetSize(), _T("Job List") );
		for (i = -1; ++i < MAX_JOBS_COLUMNS; )
		{
			if (width[i] > 5000)
				width[i] = 30;
		}
		InsertColumnHeaders( m_ColNames, width );
		m_bAlreadyGotColumns = TRUE;
        GetJobs();
    }
}


void CJobListCtrl::GetJobSpec( )
{
    CCmd_JobSpec *pCmd = new CCmd_JobSpec;
	pCmd->Init( m_hWnd, RUN_ASYNC );
	
    if( !pCmd->Run( ) )
	   	delete pCmd;
}


void CJobListCtrl::GetJobs( )
{
	CCmd_Jobs *pCmd = new CCmd_Jobs;
	pCmd->Init( m_hWnd, RUN_ASYNC);
	
    if( GET_SERVERLEVEL() == 3)
    {
        m_sFilter.Empty();
       	pCmd->SetFilter( FALSE );
    }
    else
    	pCmd->SetFilter( !m_sFilter.IsEmpty( ) );

	ASSERT(m_bAlreadyGotColumns);
	int maxFlds = m_FieldNames.GetSize();
	int maxCols = m_ColNames.GetSize();
	int i;
	for (i = -1; ++i < maxCols; )	// add the fields desired in column order
	{
		CString colName = m_ColNames.GetAt(i);
		int j;
		for (j = -1; ++j < maxFlds; )
		{
			if (m_FieldNames.GetAt(j) == colName)
			{
				pCmd->GetFieldNames().Add(colName);
				pCmd->GetFieldCodes().Add(m_ColCodes.GetAt(j));
				break;
			}
		}
		if (j >= maxFlds)
		{
			pCmd->GetFieldNames().Add(_T(""));
			pCmd->GetFieldCodes().Add(0);
		}
	}

	// Make a copy of the filter view, because CCmd_Jobs will
	// destroy that copy
	POSITION pos=m_FilterView.GetHeadPosition();
	m_StrList.RemoveAll();
	while(pos != NULL)
		m_StrList.AddTail(m_FilterView.GetNext(pos));

	if( pCmd->Run( m_sFilter, m_FastJobs, &m_StrList, m_FilterIncIntegs ) )
	{
		Clear();
		CP4ListCtrl::OnViewUpdate();
		MainFrame()->UpdateStatus( LoadStringResource(IDS_REQUESTING_JOBS_LISTING) );
	}
	else
		delete pCmd;
}


/*
	_________________________________________________________________
*/

CString CJobListCtrl::SetCaption()
{
	if( (m_sFilter.IsEmpty() && m_FilterView.IsEmpty()) || GET_SERVERLEVEL() < 4)
		m_caption = LoadStringResource(IDS_PERFORCE_JOBS);
	else
    {
        CString filter;
		if (!m_sFilter.IsEmpty() && !m_FilterView.IsEmpty())
			filter.FormatMessage(IDS_FILTERED_BOTH, m_sFilter);
		else if (!m_FilterView.IsEmpty())
		{
			CString filelist=m_FilterView.GetHead();
			if (m_FilterView.GetCount() > 1)
			{
				POSITION pos= m_FilterView.GetHeadPosition();
				m_FilterView.GetNext(pos);
				while( pos != NULL )
					filelist += _T(", ") + m_FilterView.GetNext(pos);
			}
			filter.FormatMessage(IDS_FILTERED_BY_FILES, filelist);
		}
		else
			filter.FormatMessage(IDS_FILTERED, m_sFilter);
		m_caption = LoadStringResource(IDS_PERFORCE_JOBS) + filter;
    }

	CP4PaneContent::GetView()->SetCaption();

	return m_caption;
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	// make sure window is active
	GetParentFrame()->ActivateFrame();

	///////////////////////////////
	// See ContextMenuRules.txt for order of menu commands!

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

	int	index;
    SetIndexAndPoint( index, point );

	// Can always create new job
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_NEW );

	if(index != -1)
	{
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_EDITSPEC );
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_DESCRIBE );
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_DELETE );
	}
	popMenu.AppendMenu(MF_SEPARATOR);
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_SETFILTER );
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_REMOVEFILTER );
	popMenu.AppendMenu(MF_SEPARATOR);
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_SETFILEFILTER, LoadStringResource(IDS_FILTER_JOBVIEW) );
	if (GET_P4REGPTR()->GetEnableSubChgIntegFilter())
		popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_SETFILEFILTERINTEG, LoadStringResource(IDS_FILTERINTEG_JOBVIEW) );
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_REMOVEFILEFILTER, LoadStringResource(IDS_JOB_REMOVEFILEFILTER) );
	popMenu.AppendMenu(MF_SEPARATOR);
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_JOB_CONFIGURE, LoadStringResource(IDS_JOB_CONFIGURE) );
	popMenu.AppendMenu(MF_ENABLED | MF_STRING, ID_VIEW_UPDATE, LoadStringResource(IDS_REFRESH));

	MainFrame()->AddToolsToContextMenu(&popMenu);

	popMenu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,	point.x, point.y, AfxGetMainWnd());
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::OnUpdateJobDelete(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( OnUpdateShowMenuItem( pCmdUI, IDS_DELETE_s ) );	
}

void CJobListCtrl::OnJobDelete() 
{
	OnDelete( P4JOB_DEL );
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::OnUpdateJobEditspec( CCmdUI* pCmdUI ) 
{
	pCmdUI->Enable( OnUpdateShowMenuItem(pCmdUI, IDS_EDITSPEC_s) && !m_EditInProgress );	
}

void CJobListCtrl::EditTheSpec(CString *name)
{
	OnJobEditspec(name);
}

void CJobListCtrl::OnJobEditspec() 
{
	CString str = GetSelectedItemText();
	OnJobEditspec(&str);
}

void CJobListCtrl::OnJobEditspec(CString *jobname) 
{
	m_Active = *jobname;
	m_NewJob = FALSE;
	EditSpec( ) ;
}


void CJobListCtrl::OnJobNew() 
{
	MainFrame()->ViewJobs();
	if (SERVER_BUSY())
		m_Need2DoNew = TRUE;
	else
	{
		m_Need2DoNew = FALSE;
		m_Active.Empty();
		m_NewJob = TRUE;
		EditSpec( ) ;
	}
}


void CJobListCtrl::EditSpec()
{
	if (m_EditInProgress)
	{
		CantEditRightNow(IDS_JOB);
		return;
	}

	m_pNewSpec = new CP4Job;
	
	CCmd_EditSpec *pCmd = new CCmd_EditSpec;
	pCmd->Init( m_hWnd, RUN_ASYNC, HOLD_LOCK );
	if( pCmd->Run( P4JOB_SPEC, m_Active, m_pNewSpec ) )//fanny:is this okay for edit and new?
		MainFrame()->UpdateStatus( LoadStringResource(IDS_EDITING_JOB_SPEC) );
	else
	{
		delete pCmd;
		delete m_pNewSpec;
	}
}


LRESULT CJobListCtrl::OnP4JobSpec(WPARAM wParam, LPARAM lParam)
{
	CCmd_EditSpec *pCmd= (CCmd_EditSpec *) wParam;

	// let the dialogbox know whether this is a new job or an edit of an existing one
	pCmd->SetIsRequestingNew(m_NewJob);
	pCmd->SetCaller(DYNAMIC_DOWNCAST(CView, GetParent()));
	if(!pCmd->GetError() && !m_EditInProgress && pCmd->DoSpecDlg(this))
	{
		m_EditInProgress = TRUE;
		m_EditInProgressWnd = pCmd->GetSpecSheet();
	}
	else
	{
		delete m_pNewSpec;
		if (pCmd->HaveServerLock())
			pCmd->ReleaseServerLock();
		delete pCmd;
	}
	MainFrame()->ClearStatus();
	return 0;
}

LRESULT CJobListCtrl::OnP4EndSpecEdit( WPARAM wParam, LPARAM lParam )
{
	CCmd_EditSpec *pCmd= (CCmd_EditSpec *) wParam;
	int index;

	if (lParam != IDCANCEL && lParam != IDABORT && lParam != IDRETRY)
	{
		m_pNewSpec->SetJobName(pCmd->GetNewJobName());

		if(m_NewJob)
		{
			LPCTSTR statustext = m_pNewSpec->GetStatusText();
			if (!*statustext)
				m_pNewSpec->SetJobStatus(JOB_OPEN);
		}

		m_pNewSpec->ConvertToColumns(m_ColCodes, m_ColNames, m_FieldNames);
		if(m_NewJob & (FindInList(pCmd->GetNewJobName()) == -1) )
		{
			InsertJob(m_pNewSpec, GetItemCount());
			ReSort();
			index= FindInList(pCmd->GetNewJobName());
		}
		else
		{
			index= FindInList(pCmd->GetNewJobName());
			if (index != -1)
				UpdateJob(m_pNewSpec, index);
			else if (m_ColNames.GetSize())	// Has job pane been initialized?
			{
				InsertJob(m_pNewSpec, GetItemCount());
				ReSort();
				index= FindInList(pCmd->GetNewJobName());
			}
			else
				delete m_pNewSpec;
		}
		EnsureVisible( index, TRUE );
		SetItemState( index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED );
	}
	else
		delete m_pNewSpec;

	if (lParam != IDABORT)
	{
		MainFrame()->ClearStatus();
		if (pCmd->HaveServerLock())
			pCmd->ReleaseServerLock();
		CDialog *dlg = (CDialog *)pCmd->GetSpecSheet();
		dlg->DestroyWindow();
	}
	delete pCmd;
	m_EditInProgress = FALSE;
	return 0;
}

//////////////////////////////////////////////////////////////////////////
// Sort callback, not in class


int CJobListCtrl::OnCompareItems(LPARAM lParam1, LPARAM lParam2, int subItem)
{
    ASSERT(lParam1 && lParam2);
	CString txt1= ((CP4Job const *)lParam1)->GetJobField(subItem);
	CString txt2= ((CP4Job const *)lParam2)->GetJobField(subItem);

	int rc;

	if(m_SortAscending)
		rc = txt1.Compare(txt2);
	else
		rc = txt2.Compare(txt1);

	return rc;
}

/*
	_________________________________________________________________
*/

void CJobListCtrl::OnDeleteitem(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	
	delete (CP4Job *) GetItemData(pNMListView->iItem);
			
	*pResult = 0;
}


/*
	_________________________________________________________________
*/

void CJobListCtrl::OnUpdateJobSetFilter(CCmdUI* pCmdUI) 
{
	pCmdUI->SetText ( LoadStringResource( IDS_FILTER ) );
	pCmdUI->Enable(MainFrame()->SetMenuIcon(pCmdUI, GET_SERVERLEVEL() > 3 && !SERVER_BUSY()));
}

LRESULT CJobListCtrl::OnJobFilter2(WPARAM, LPARAM) 
{
	OnJobSetFilter();
	return 0;
}

void CJobListCtrl::OnJobSetFilter() 
{
	RECT	rect;

	CJobFilter dlg;
	dlg.SetFilterString ( m_sFilter );
	GetWindowRect(&rect);										// we want to position filter dialog box
	dlg.m_top  = rect.top + GetSystemMetrics(SM_CYCAPTION);		// at the top left of this pane, so pass
	dlg.m_left = rect.left + 2;									// it our current screen location
	dlg.m_right= rect.right;
	MainFrame()->DoNotAutoPoll();
	dlg.DoModal( );
	MainFrame()->ResumeAutoPoll();
	if ( m_sFilter != dlg.GetFilterString ( ) )
	{
		m_sFilter = dlg.GetFilterString ( );
		PersistentJobFilter( KEY_WRITE );
		OnViewUpdate( );	
	}
}

void CJobListCtrl::OnUpdateJobRemovefilter(CCmdUI* pCmdUI) 
{
	pCmdUI->SetText ( LoadStringResource( IDS_CLEARFILTER ) );
	pCmdUI->Enable(MainFrame()->SetMenuIcon(pCmdUI, GET_SERVERLEVEL() > 3 
		&& !SERVER_BUSY() && !m_sFilter.IsEmpty()));
}

void CJobListCtrl::OnJobRemovefilter() 
{
	m_sFilter.Empty ( );
	PersistentJobFilter( KEY_WRITE );
	OnViewUpdate( );
}


void CJobListCtrl::PersistentJobFilter( REGSAM accessmask )
{
	HKEY hKey = NULL;
	CString sKey = _T("Software\\Perforce\\P4Win\\");
	CString sEntry = _T("JobFilter");
	DWORD disposition;

	if ( RegCreateKeyEx( HKEY_CURRENT_USER, sKey,
							0, NULL,
							REG_OPTION_NON_VOLATILE,
							accessmask, NULL,
							&hKey, &disposition ) == ERROR_SUCCESS )
	{
		const DWORD lenFilter = 512;

		if ( accessmask == KEY_WRITE )
		{
			RegSetValueEx( hKey, sEntry, NULL, REG_SZ
				, (LPBYTE)(LPCTSTR) m_sFilter, m_sFilter.GetLength( ) * sizeof(TCHAR) + 1);
		}
		else
		{
			TCHAR buf[ lenFilter ];
			DWORD cbData = sizeof(buf);
			if ( (RegQueryValueEx( hKey, sEntry, NULL, NULL, 
									(LPBYTE)buf, &cbData ) == ERROR_SUCCESS) && cbData )
			{
				if(!cbData)
					cbData = 1;
				buf[cbData-1] = _T('\0');
				m_sFilter = buf;
			}
		}

		RegCloseKey( hKey );
	}
}

void CJobListCtrl::OnUpdateJobConfigure(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(MainFrame()->SetMenuIcon(pCmdUI, !SERVER_BUSY()));
}

int CJobListCtrl::GetFieldNbr( CString str, const CString &spec )
{
	//  Get the field names from the spec. they are between the word 
    //  "Fields:" and the word "Required" strip the separator.  Don't
    //  crash if the jobspec is not recognized.

    CString errorText;
    CString fields;
    
	int start= spec.Find(_T("Fields:")) + lstrlen(_T("Fields:"));
    int end= spec.Find(_T("Required:"));
    if( start == -1 || end == -1 || end < start || end >= spec.GetLength())
        errorText=LoadStringResource(IDS_UNABLE_TO_FIND_FIELD_DELIMITERS);
    
    if( errorText.IsEmpty() )
    {
	    fields = spec.Mid(start, end - start);
	    start= fields.Find( _T('\t') );
        if( start == -1 )
            errorText=LoadStringResource(IDS_UNABLE_TO_FIND_INITIAL_FIELD_DELIMITER);
    }

    if( errorText.IsEmpty() )
    {
	    fields = fields.Mid( start + 1 );

	    // Pick out the requested field names if present
	    
	    int code;
	    CString field;
    	do  
	    {
		    //		get the field spec (e.g., "101 Job word 32")
		    //		and truncate the fields.
		    //
		    field = fields.Left ( fields.Find( _T('\n') ) + 1 );
		    fields = fields.Right ( fields.GetLength( ) - field.Find ( _T('\n') ) - 1 );

		    //		get the "101" part of the spec in code
		    //		and the "Job" part in field.
		    //
			int i = field.Find( _T(' ') );
			if (i == -1)
				return 0x7FFF;
		    code = _ttoi ( field.Left ( i ) ) ;
		    field = field.Right ( field.GetLength ( ) - field.Find( _T(' ') ) - 1 );
		    field = field.Left ( field.Find ( _T(' ') ) );

			if (str == field)
                return code;
	    }
	    while ( !field.IsEmpty( ) );
    }

    if( !errorText.IsEmpty() )
    {
		// Very unlikely that we will ever report this error, but an error
		// message is always preferable to an inexplicable crash
		AddToStatus(CString(_T("JobView::GetFldNbr()") + errorText), SV_ERROR);
	}
    return 0x7FFF;
}

void CJobListCtrl::OnJobConfigure() 
{
	int rc;
	CJobsConfigure dlg;
	int maxcols = m_ColNames.GetSize();
	if (!maxcols && !m_Need2CallOnJobConfigure)
	{
		m_Need2CallOnJobConfigure = TRUE;
		GetJobSpec( );
		return;
	}
	m_Need2CallOnJobConfigure = FALSE;

	CStringArray colNames;
	colNames.SetSize(maxcols);
    CArray<int, int> colWidths;
	colWidths.SetSize(maxcols);
	int subItem;
	for(subItem=0; subItem < maxcols; subItem++)
	{
		dlg.m_ColNames += m_ColNames.GetAt(subItem) + _T(' ');
		colWidths.SetAt(subItem, GetColumnWidth(subItem));
		colNames.SetAt(subItem, m_ColNames.GetAt(subItem));
	}
	int maxflds = m_SpecNames.GetSize();
	for(subItem=0; subItem < maxflds; subItem++)
		dlg.m_SpecNames += m_SpecNames.GetAt(subItem) + _T(' ');
	if ((rc = dlg.DoModal( )) != IDCANCEL)
	{
		BOOL need2save = FALSE;
		CString newWidths;
		CString newSorts;
		if (rc == IDOK)
		{
			int i, j;
			CString num;
			CString newNames;
			int newSortCols[MAX_SORT_COLUMNS] = {0,0,0,0};
			int tokenctr = 1;
			CTokenString tokstr;
			CString token;
			tokstr.Create(dlg.m_ColNames);
			tokstr.PrepareParse( );
			token=tokstr.GetToken();  
			while(!token.IsEmpty())
			{
				num = _T("50");
				for (i = -1; ++i < maxcols; )
				{
					if (m_ColNames.GetAt(i) == token)
					{
						int k;
						for (k=-1; ++k < maxcols; )
						{
							if (colNames.GetAt(k) == token)
								break;
						}
						if (k < maxcols)
							num.Format(_T("%d"), colWidths.GetAt(k));
						for (j=-1, ++i; ++j < MAX_SORT_COLUMNS; )
						{
							if (abs(m_SortColumns[j]) == i)
							{
								newSortCols[j] = m_SortColumns[j] < 0 ? -tokenctr : tokenctr;
								break;
							}
						}
						break;
					}
				}
			    if(!newWidths.IsEmpty())
				    newWidths += _T(",");
				newWidths += num;
				int n = GetFieldNbr(token, m_Spec);
				if (n < 106)
					token.Format(_T(":%d"), n);
				newNames += token + _T(' ');
				token=tokstr.GetToken();
				tokenctr++;
			}
			SaveColumnNames(newNames, _T("Job List"));
			for (i = -1; ++i < MAX_SORT_COLUMNS; )
			{
				num.Format(_T("%d"), newSortCols[i]);
				if(i)
					newSorts+=_T(",");
				newSorts+=num;
			}
			need2save = TRUE;
		}
		else if (rc == IDIGNORE)
		{
			DeleteColumnNames(_T("Job List"));
			newWidths = _T("90,80,60,90,200");
			newSorts  = _T("-1,0,0,0");
			need2save = TRUE;
		}
		if (need2save)
		{
			// Save the column widths
			CString theKey = sRegKey;
			CRegKeyEx key;
			if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, theKey))
			{
				key.SetValueString(newWidths, sRegValue_ColumnWidths);
	            key.SetValueString(newSorts, sRegValue_SortColumns);
				m_ReadSavedWidths = FALSE;	
			}
		}
		m_DesiredCols.RemoveAll();
		m_DesiredCols.SetSize(0);
		m_FastJobs = GetSavedColumnNames(m_DesiredCols, _T("Job List"));
		m_FieldNames.SetSize(0);
		GetFldNames( m_FieldNames, m_Spec );
		OnViewUpdate( );
	}
}

void CJobListCtrl::OnDescribeJob() 
{
	//		let user type in the job name. if it's blank the user bailed.
	//
	CJobDescribe dlg;
	dlg.m_JobStr = m_Describing;
	if( dlg.DoModal( ) == IDCANCEL )
		return;

	m_Describing = dlg.GetJobStr( ) ;
	if ( m_Describing.IsEmpty( ) )
		return;

	m_Active = GetSelectedItemText();	// so we can see whether to show the Edit button or not

	CCmd_Describe *pCmd= new CCmd_Describe;
	pCmd->Init( m_hWnd, RUN_ASYNC);
	if( pCmd->Run( P4JOB_SPEC, m_Describing) )
		MainFrame()->UpdateStatus(LoadStringResource(IDS_FETCHING_JOB_SPEC));
	else
		delete pCmd;
}

BOOL CJobListCtrl::TryDragDrop( )
{
	// Store the job this is from
	m_DragFromItemName = GetSelectedItemText();

	m_OLESource.DelayRenderData( (unsigned short) m_CF_JOB);

	return m_OLESource.DoDragDrop(DROPEFFECT_COPY, &m_DragSourceRect, NULL)
			== DROPEFFECT_NONE ? FALSE : TRUE;
}

/////////////////////////////////////////////////////////////////////
// OLE drag-drop support, to accept depot files or folders
// or accept user or client names which will
// define a view to be used to filter the submitted
// changes that this window displays.
// Also can drop Jobs to be Fixed.
/////////////////////////////////////////////////////////////////////

DROPEFFECT CJobListCtrl::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) 
{
	m_DropEffect=DROPEFFECT_NONE;
	m_DragDataFormat=0;

	// Dont allow a drop if the server is busy, since a drop immediately attempts to
	// invoke a server command
	if(SERVER_BUSY())
		return DROPEFFECT_NONE;
		
	if(pDataObject->IsDataAvailable( (unsigned short) m_CF_DEPOT))
	{
		m_DropEffect=DROPEFFECT_COPY;
		m_DragDataFormat=m_CF_DEPOT;
	}
#ifdef UNICODE
	else if(pDataObject->IsDataAvailable( (unsigned short) CF_UNICODETEXT))
	{
		m_DropEffect=DROPEFFECT_COPY;
		m_DragDataFormat=CF_UNICODETEXT;
	}
#else
	else if(pDataObject->IsDataAvailable( (unsigned short) CF_TEXT))
	{
		m_DropEffect=DROPEFFECT_COPY;
		m_DragDataFormat=CF_TEXT;
	}
#endif
	return m_DropEffect;
}

DROPEFFECT CJobListCtrl::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) 
{
	// Dont allow a drop if the server is busy, since a drop immediately attempts to
	// invoke a server command
	if(SERVER_BUSY())
		m_DropEffect= DROPEFFECT_NONE;
		
	return m_DropEffect;
}

BOOL CJobListCtrl::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) 
{
	CString fname;
	
	if(SERVER_BUSY())
	{
		// OnDragEnter() and OnDragOver() should avoid a drop at 
		// the wrong time!
		ASSERT(0);
		return FALSE;
	}
	
	if(m_DragDataFormat == m_CF_DEPOT)
	{
		ClientToScreen(&point);
		::SendMessage(m_depotWnd, WM_DROPTARGET, JOBVIEW, MAKELPARAM(point.x,point.y));
		return TRUE;
	}
#ifdef UNICODE
	if(m_DragDataFormat == CF_UNICODETEXT)
	{
		HGLOBAL hGlob = pDataObject->GetGlobalData(CF_UNICODETEXT);
#else
	if(m_DragDataFormat == CF_TEXT)
	{
		HGLOBAL hGlob = pDataObject->GetGlobalData(CF_TEXT);
#endif
		LPCTSTR p;

		if ((hGlob != NULL)	&& ((p = (LPCTSTR)::GlobalLock(hGlob)) != NULL))
		{
			CString itemStr = p;
			::GlobalUnlock(hGlob);
			OnEditPaste( itemStr );
		}
		return TRUE;
	}
	// Return false, so depot window doesnt start a file-open operation
	return FALSE;
}

int CJobListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CP4ListCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	m_sFilter.Empty( );
	PersistentJobFilter( KEY_READ );

	return 0;
}


void CJobListCtrl::OnJobSetFileFilter() 
{
    if( !SERVER_BUSY() )
    {
	    ::SendMessage(m_depotWnd, WM_GETSELLIST, (WPARAM) &m_FilterView, 0);
		m_FilterIncIntegs = FALSE;
	    OnViewUpdate();
    }
}

void CJobListCtrl::OnJobSetFileFilterInteg() 
{
    if( !SERVER_BUSY() )
    {
	    ::SendMessage(m_depotWnd, WM_GETSELLIST, (WPARAM) &m_FilterView, 0);
		m_FilterIncIntegs = GET_P4REGPTR()->GetEnableSubChgIntegFilter();
	    OnViewUpdate();
    }
}

void CJobListCtrl::OnUpdateJobSetFileFilter(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable( !SERVER_BUSY() 
			&& (pCmdUI->m_nID != ID_JOB_SETFILEFILTERINTEG 
			 || GET_P4REGPTR()->GetEnableSubChgIntegFilter())
			&& ::SendMessage(m_depotWnd, WM_GETSELCOUNT, 0, 0) > 0 );
}

void CJobListCtrl::OnUpdateJobRemoveFileFilter(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(!SERVER_BUSY() && !m_FilterView.IsEmpty());
}

void CJobListCtrl::OnJobRemoveFileFilter() 
{
	m_FilterView.RemoveAll(); 
	m_FilterIncIntegs = FALSE;
	OnViewUpdate( );
}

void CJobListCtrl::OnUpdateSetFilterJobs(CCmdUI* pCmdUI)
{
	pCmdUI->Enable( !SERVER_BUSY() && 
		// can do expression filtering
		(GET_SERVERLEVEL() > 3 ||	
		// or files selected for filter by files
		 ::SendMessage(m_depotWnd, WM_GETSELCOUNT, 0, 0) > 0 ));
}

void CJobListCtrl::OnUpdateClearFilterJobs(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(!SERVER_BUSY() && 
		(!m_FilterView.IsEmpty() || !m_sFilter.IsEmpty()));
}

void CJobListCtrl::OnPerforceOptions()
{
	MainFrame()->OnPerforceOptions(TRUE, FALSE, IDS_PAGE_JOB);
}