// JobListDlg.cpp : implementation file // #include "stdafx.h" #include "p4win.h" #include "JobListDlg.h" #include "JobView.h" #include "P4Job.h" #include "catchalldlg.h" #include "MainFrm.h" #include "StringUtil.h" #include "RegKeyEx.h" #include "hlp\p4win.hh" #include "ImageList.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif static LPCTSTR sRegKey = _T("Software\\Perforce\\P4Win\\Layout\\Job List"); static LPCTSTR sRegValue_ColumnWidths = _T("Column Widths"); static LPCTSTR sRegValue_SortColumns = _T("Sort Columns"); #define UPDATE_STATUS(x) ((CMainFrame *)AfxGetMainWnd())->UpdateStatus(x) int CALLBACK JobListDlgSort(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); #define IMG_INDEX(x) (x-IDB_PERFORCE) // Module global for use in sort callback CJobListDlg *pDlg; ///////////////////////////////////////////////////////////////////////////// // CJobListDlg dialog CJobListDlg::CJobListDlg(CWnd* pParent /*=NULL*/) : CDialog(CJobListDlg::IDD, pParent) { //{{AFX_DATA_INIT(CJobListDlg) //}}AFX_DATA_INIT m_SortAscending=FALSE; m_LastSortColumn=0; pDlg=this; m_InitRect.SetRect(0,0,100,100); m_WinPos.SetWindow( this, _T("JobListDlg") ); for (int i = -1; ++i < MAX_SORT_COLUMNS; ) m_SortColumns[i] = 0; } void CJobListDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CJobListDlg) DDX_Control(pDX, IDC_JOBSTATUS, m_JobStatus); DDX_Control(pDX, IDC_JOBLIST, m_JobListCtrl); DDX_Control(pDX, IDC_JOBDESC, m_JobDesc); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CJobListDlg, CDialog) //{{AFX_MSG_MAP(CJobListDlg) ON_WM_SIZE() ON_WM_GETMINMAXINFO() ON_NOTIFY(LVN_ITEMCHANGED, IDC_JOBLIST, OnItemchangedJoblist) ON_NOTIFY(LVN_COLUMNCLICK, IDC_JOBLIST, OnColumnclickJoblist) ON_NOTIFY(NM_DBLCLK, IDC_JOBLIST, OnDblclickJoblist) ON_BN_CLICKED(IDHELP, OnHelp) ON_BN_CLICKED(IDC_FILTER, OnJobFilter) ON_BN_CLICKED(ID_CLEARFILTER, OnClearFilter) ON_WM_HELPINFO() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CJobListDlg message handlers BOOL CJobListDlg::OnInitDialog() { CDialog::OnInitDialog(); CP4Job *job; CString str; SetFont(m_Font); // Modify the list control style so that the entire selected row is highlighted DWORD dwStyle = ::SendMessage(m_JobListCtrl.m_hWnd,LVM_GETEXTENDEDLISTVIEWSTYLE,0,0); dwStyle |= LVS_EX_FULLROWSELECT; ::SendMessage(m_JobListCtrl.m_hWnd,LVM_SETEXTENDEDLISTVIEWSTYLE,0,dwStyle); // Make sure list control shows selection when not the focused control m_JobListCtrl.ModifyStyle(0, LVS_SHOWSELALWAYS, 0); // Set the imagelist m_JobListCtrl.SetImageList(TheApp()->GetImageList(), LVSIL_SMALL); // Get original size of control CRect rect; m_JobListCtrl.GetWindowRect(&rect); int colwidth[MAX_JOBS_COLUMNS]={90,80,60,90,200}; // Record the initial window size, and then see if there is a registry preference GetWindowRect(&m_InitRect); m_WinPos.RestoreWindowPosition(); // 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_JobListCtrl.GetWindowRect(&rect); // Get any saved column widths from registry RestoreSavedWidths(colwidth, MAX_JOBS_COLUMNS); // Make sure no column completely disappeared (because you can't get it back then) for (int i=-1; ++i < MAX_JOBS_COLUMNS; ) { if (colwidth[i] < 5) colwidth[i] = 5; } // Show the current job filter CString txt; m_sFilter = PersistentJobFilter(KEY_READ); if(m_sFilter.GetLength() > 0) { txt.FormatMessage(IDS_JOBFILTERSTR, m_sFilter); } else { txt = LoadStringResource(IDS_ALLJOBS); GetDlgItem(ID_CLEARFILTER)->EnableWindow(FALSE); } GetDlgItem(IDC_FILTER_TEXT)->SetWindowText(txt); // Use the same font as the calling window m_JobListCtrl.SetFont(m_Font); // Insert the columns int maxcols = m_ColNames->GetSize(); int width=GetSystemMetrics(SM_CXVSCROLL);; int retval; LV_COLUMN lvCol; int subItem; for(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=rect.Width() - width - 4; // expand last column to fill window retval=m_JobListCtrl.InsertColumn(subItem, &lvCol); } // Add the data LV_ITEM lvItem; int iActualItem = -1; POSITION pos= m_pJobList->GetHeadPosition(); int iItem; for(iItem=0; iItem < m_pJobList->GetCount(); iItem++) { job= (CP4Job *) m_pJobList->GetNext(pos); for(subItem=0; subItem < maxcols; 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.iImage = CP4ViewImageList::VI_JOB; lvItem.lParam=(LPARAM) job; txt=PadCRs(job->GetJobField(subItem)); lvItem.pszText = const_cast( (LPCTSTR ) txt); if(subItem==0) iActualItem=m_JobListCtrl.InsertItem(&lvItem); else m_JobListCtrl.SetItem(&lvItem); } } if(m_pJobList->GetCount()) { // Sort the jobs list the same way the JobView is sorted m_JobListCtrl.SortItems( JobListDlgSort, m_LastSortColumn ); // Find the job currently selected in the jobs pane iItem=0; if (!m_CurrJob->IsEmpty()) { TCHAR cur[ LISTVIEWNAMEBUFSIZE + 1 ]; TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; lstrcpy(cur, m_CurrJob->GetBuffer(m_CurrJob->GetLength()+1)); m_CurrJob->ReleaseBuffer(-1); for( ; iItem < ListView_GetItemCount( m_JobListCtrl.m_hWnd ); iItem++ ) { ListView_GetItemText( m_JobListCtrl.m_hWnd, iItem, 0, str, LISTVIEWNAMEBUFSIZE ); if( !Compare(cur, str) ) break; } if (iItem >= ListView_GetItemCount( m_JobListCtrl.m_hWnd )) iItem = 0; } // Make sure the same job is selected and visible m_JobListCtrl.SetItemState(iItem, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); m_JobListCtrl.EnsureVisible( iItem, FALSE ); // Set change description to match selected item in list job= (CP4Job *) m_JobListCtrl.GetItemData(iItem); str= job->GetDescription(); str.Replace(_T("\n"), _T("\r\n")); m_JobDesc.SetWindowText(str); } else if( m_sFilter.IsEmpty() ) { AfxMessageBox(IDS_NO_JOBS_AVAILABLE_FOR_FIXING, MB_ICONEXCLAMATION); EndDialog(IDCANCEL); } else AddToStatus(LoadStringResource(IDS_USEJOBFILTERBUTTON), SV_MSG); if (!LoadJobStatusComboBox()) { GetDlgItem(IDC_JOBSTATUSPROMPT)->ShowWindow( SW_HIDE ); GetDlgItem(IDC_JOBSTATUS)->ShowWindow( SW_HIDE ); } // And finally, set focus to the list control so that the first 'down' // keystroke can be used to scroll down m_JobListCtrl.SetFocus(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } CString CJobListDlg::PersistentJobFilter( REGSAM accessmask ) { HKEY hKey = NULL; const CString sKey = _T("Software\\Perforce\\P4Win\\"); const CString sEntry = _T("JobFilter"); CString filter = _T(""); 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'); filter = buf; } } RegCloseKey( hKey ); } return filter; } BOOL CJobListDlg::LoadJobStatusComboBox() { int i; m_JobStatus.AddString(_T("(default)")); if (GET_SERVERLEVEL() < 10) return FALSE; i = m_pJobSpec->Find(_T("\nFields:\n\t")); if (i == -1) return FALSE; i = m_pJobSpec->Find(_T("\n\t102 "), i); if (i == -1) return FALSE; i += sizeof(_T("\n\t102 "))/sizeof(TCHAR) - 1; int j = m_pJobSpec->Find(_T(' '), i); if (j == -1) return FALSE; CString name = m_pJobSpec->Mid(i, j-i); i = m_pJobSpec->Find(_T("\nValues:"), j); if (i == -1) { CString dashed = _T("\nValues"); dashed += _T("-") + name; i = m_pJobSpec->Find(dashed, j); if (i == -1) return FALSE; } i = m_pJobSpec->Find(name, i); if (i == -1) return FALSE; i += name.GetLength(); while (1) { TCHAR c = m_pJobSpec->GetAt(++i); if ((c != _T(':')) && (c != _T(' ')) && (c != _T('\t'))) break; } j = m_pJobSpec->Find(_T('\n'), i); if (j == -1) return FALSE; CString values = m_pJobSpec->Mid(i, j-i); while((i = values.Find(_T('/'))) != -1) { CString value = values.Mid(0,i); m_JobStatus.AddString(value); values = values.Mid(i+1); } m_JobStatus.AddString(values); return TRUE; } void CJobListDlg::SetJobList(CObList *joblist) { ASSERT_KINDOF(CObList,joblist); m_pJobList= joblist; } void CJobListDlg::SetJobSpec(CString *jobSpec) { m_pJobSpec= jobSpec; } void CJobListDlg::SetJobCols(CStringArray *jobCols) { m_ColNames= jobCols; } void CJobListDlg::SetJobCurr(CString *jobName) { m_CurrJob= jobName; } void CJobListDlg::OnOK() { int cursel = m_JobStatus.GetCurSel(); if (cursel != CB_ERR) m_JobStatus.GetLBText(cursel, m_JobStatusValue); if (m_JobStatusValue == _T("(default)")) m_JobStatusValue.Empty(); m_WinPos.SaveWindowPosition(); m_SelectedJobs.RemoveAll(); int nItem=m_JobListCtrl.GetNextItem( -1, LVNI_ALL | LVNI_SELECTED ); while(nItem != -1) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; ListView_GetItemText( m_JobListCtrl.m_hWnd, nItem, 0, str, LISTVIEWNAMEBUFSIZE ); m_SelectedJobs.AddHead(str); nItem=m_JobListCtrl.GetNextItem( nItem, LVNI_ALL | LVNI_SELECTED ); } EndDialog(IDOK); } void CJobListDlg::OnItemchangedJoblist(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; CString txt, txt1; // Update display of revision description if(pNMListView->uNewState==3) { txt=((CP4Job *) pNMListView->lParam)->GetDescription(); txt.Replace(_T("\n"), _T("\r\n")); m_JobDesc.SetWindowText(txt); } *pResult = 0; } void CJobListDlg::OnCancel() { m_WinPos.SaveWindowPosition(); CDialog::OnCancel(); } void CJobListDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); // Slide the OK and Cancel buttons to the bottom of dlg CWnd *pOK = GetDlgItem(IDOK); CWnd *pCancel = GetDlgItem(IDCANCEL); CWnd *pHelp = GetDlgItem(IDHELP); CWnd *pFilter = GetDlgItem(IDC_FILTER); CWnd *pClear = GetDlgItem(ID_CLEARFILTER); if(!pOK) return; CRect rect; int fw, fh; int x = cx - 5; int y = cy - 5; pFilter->GetClientRect(&rect); pFilter->MoveWindow(x - (rect.Width()*2) - 5, 0, fw = rect.Width(), fh = rect.Height(), TRUE); pClear->GetClientRect(&rect); pClear->MoveWindow(x - rect.Width(), 0, rect.Width(), rect.Height(), TRUE); pHelp->GetClientRect(&rect); pHelp->MoveWindow(x - rect.Width(), y - rect.Height(), rect.Width(), rect.Height(), TRUE); x -= rect.Width() + 5; pCancel->GetClientRect(&rect); pCancel->MoveWindow(x - rect.Width(), y - rect.Height(), rect.Width(), rect.Height(), TRUE); x -= rect.Width() + 5; pOK->GetClientRect(&rect); pOK->MoveWindow(x - rect.Width(), y - rect.Height(), rect.Width(), rect.Height(), TRUE); // leave space between buttons and description edit control y -= rect.Height() + 5; // position the filter string CWnd *pFltTxt = GetDlgItem(IDC_FILTER_TEXT); pFltTxt->GetWindowRect(&rect); pFltTxt->MoveWindow(5, (fh-rect.Height())/2+1, cx - 10 - (fw*2) - 6, rect.Height(), TRUE); // position the label CWnd *pLabel = GetDlgItem(IDC_SUMMARYLABEL); pLabel->GetClientRect(&rect); int remainder = y - (rect.Height() + 5 + 5); // Position the text box CWnd *pText = GetDlgItem(IDC_JOBDESC); int h = remainder / 4; pText->MoveWindow(2, y - h, cx-4, h, TRUE); y -= h + 5; // position the label pLabel->GetClientRect(&rect); pLabel->MoveWindow(2, y - rect.Height(), rect.Width(), rect.Height(), TRUE); // Position the Job Status fields CWnd *pList = GetDlgItem(IDC_JOBSTATUS); pList->GetClientRect(&rect); x = cx - 2; pList->MoveWindow(x - rect.Width(), y - rect.Height(), rect.Width(), rect.Height(), TRUE); x -= rect.Width() + 1; pLabel = GetDlgItem(IDC_JOBSTATUSPROMPT); pLabel->GetClientRect(&rect); pLabel->MoveWindow(x - rect.Width(), y - rect.Height(), rect.Width(), rect.Height(), TRUE); y -= rect.Height() + 5; // Increase the size of the list pList = GetDlgItem(IDC_JOBLIST); pList->MoveWindow(0, fh+5, cx, y-fh-5, TRUE); RedrawWindow(); } void CJobListDlg::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { lpMMI->ptMinTrackSize.x= m_InitRect.Width(); lpMMI->ptMinTrackSize.y= m_InitRect.Height(); } void CJobListDlg::OnDblclickJoblist(NMHDR* pNMHDR, LRESULT* pResult) { *pResult = 0; OnOK(); } void CJobListDlg::OnColumnclickJoblist(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if(pNMListView->iSubItem != m_LastSortColumn) m_LastSortColumn=pNMListView->iSubItem; else m_SortAscending= !m_SortAscending; if(pNMListView->iItem == -1) m_JobListCtrl.SortItems( JobListDlgSort,(DWORD)pNMListView->iSubItem); *pResult = 0; } // Since a sort column can only appear once in our saved column array, // given the current column, we can determine the next column. // Note that incoming 'colnbr' is a non-negative 0-relative number and must be incremented // The return value is a signed 1-relative number; negative for descending, positive for ascending int CJobListDlg::NextSortColumn(int lastcol) { if (lastcol == 0) return 0; // 0 => no next column lastcol++; for (int i = -1; ++i < (MAX_SORT_COLUMNS-1); ) { if (abs(m_SortColumns[i]) == lastcol) return m_SortColumns[i+1]; } return 0; // 0 => no next column } int CALLBACK JobListDlgSort(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { CString txt1= ((CP4Job const *)lParam1)->GetJobField(lParamSort); CString txt2= ((CP4Job const *)lParam2)->GetJobField(lParamSort); int rc; int nextcol; if(pDlg->IsSortAscending()) rc = txt1.Compare(txt2); else rc = txt2.Compare(txt1); // check for duplicate keys; if so, sort on next sort columns if (!rc && lParamSort && ((nextcol = pDlg->NextSortColumn(lParamSort)) != 0)) { // nextcol now contains a value like 1 for col-0-ascending or like -1 for col-0-decending BOOL saveSortAscending = pDlg->IsSortAscending(); if (nextcol < 0) { nextcol = 0 - nextcol; pDlg->SetSortAscending(FALSE); } else pDlg->SetSortAscending(TRUE); rc = JobListDlgSort(lParam1, lParam2, (LPARAM)(nextcol-1)); pDlg->SetSortAscending(saveSortAscending); } return rc; } // Check the registry to see if we have recorded the // column widths lastused for the job pane's list view. // Note that "HKEY_CURRENT_USER\Software\perforce\P4win\Layout\JobListDlg\Column Widths" // is no longer used because we use the "...\Job List\Column Widths" now. void CJobListDlg::RestoreSavedWidths(int *width, int numcols) { CRegKeyEx key; if(ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, sRegKey, 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); } if(!sortCols.IsEmpty()) { for(int i=0; i< MAX_SORT_COLUMNS; i++) { m_SortColumns[i]= GetANumber(sortCols); if(!i && !m_SortColumns[i]) m_SortColumns[i] = 1; } m_LastSortColumn= abs(m_SortColumns[0]) - 1; // SortColumns are signed & 1-relative m_SortAscending = m_SortColumns[0] >= 0 ? TRUE : FALSE; } } } void CJobListDlg::OnJobFilter() { 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)*2; // 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 ); // Turn off all painting in children of main window to prevent flashing EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, FALSE); SET_BUSYCURSOR(); EndDialog(IDRETRY); } } void CJobListDlg::OnClearFilter() { if (!m_sFilter.IsEmpty()) { m_sFilter.Empty(); PersistentJobFilter( KEY_WRITE ); // Turn off all painting in children of main window to prevent flashing EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, FALSE); SET_BUSYCURSOR(); EndDialog(IDRETRY); } else { GotoDlgCtrl(GetDlgItem(IDC_JOBLIST)); } } void CJobListDlg::OnHelp() { AfxGetApp()->WinHelp(TASK_CLOSING_JOBS_USING_CHANGELISTS); } BOOL CJobListDlg::OnHelpInfo(HELPINFO* pHelpInfo) { OnHelp(); return TRUE; }