// P4ListCtrl.cpp : implementation file // #include "stdafx.h" #include "p4win.h" #include "P4ListCtrl.h" #include "P4PaneView.h" #include "MainFrm.h" #include "P4Object.h" #include "P4ListBrowse.h" #include "cmd_delete.h" #include "cmd_editspec.h" #include "SpecDescDlg.h" #include "RegKeyEx.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif static LPCTSTR sRegKey = _T("Software\\Perforce\\P4Win\\Layout\\"); static LPCTSTR sRegValue_ColumnWidths = _T("Column Widths"); static LPCTSTR sRegValue_SortColumns = _T("Sort Columns"); static LPCTSTR sRegValue_ColumnNames = _T("Column Names"); ///////////////////////////////////////////////////////////////////////////// // CP4ListCtrl IMPLEMENT_DYNCREATE(CP4ListCtrl, CListCtrl) CP4ListCtrl::CP4ListCtrl() { m_UpdateState= LIST_CLEAR; m_SortAscending=FALSE; m_ActivatedBefore=FALSE; m_EditInProgress=FALSE; m_LastSortCol=0; m_ContextContext= KEYSTROKED; m_ReadSavedWidths = m_ColsInited = FALSE; m_PostViewUpdateMsg = 0; m_LastSelIx = -1; for (int i = -1; ++i < MAX_SORT_COLUMNS; ) m_SortColumns[i] = 0; } CP4ListCtrl::~CP4ListCtrl() { } void CP4ListCtrl::OnShowWindow(BOOL bShow, UINT nStatus) { if (m_EditInProgress && (bShow || GET_P4REGPTR()->AutoMinEditDlg())) m_EditInProgressWnd->ShowWindow(bShow ? SW_RESTORE : SW_SHOWMINNOACTIVE); } LRESULT CP4ListCtrl::OnActivateModeless(WPARAM wParam, LPARAM lParam) { if (m_EditInProgress && wParam == WA_ACTIVE) ::SetFocus(m_EditInProgressWnd->m_hWnd); return 0; } void CP4ListCtrl::SetUpdateDone() { m_UpdateState = LIST_UPDATED; MainFrame()->SetLastUpdateTime(UPDATE_SUCCESS); } void CP4ListCtrl::SetUpdateFailed() { m_UpdateState = LIST_CLEAR; MainFrame()->SetLastUpdateTime(UPDATE_FAILED); } // Last line of OnViewUpdate() should ALWAYS call this base fn void CP4ListCtrl::OnViewUpdate() { m_UpdateState= LIST_UPDATING; } BEGIN_MESSAGE_MAP(CP4ListCtrl, CListCtrl) //{{AFX_MSG_MAP(CP4ListCtrl) ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick) ON_WM_SHOWWINDOW() ON_WM_SETCURSOR() ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() ON_WM_RBUTTONUP() ON_WM_DESTROY() ON_NOTIFY_REFLECT(LVN_KEYDOWN, OnKeydown) ON_WM_CREATE() ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy) ON_COMMAND(ID_EDIT_COPY, OnEditCopy) //}}AFX_MSG_MAP ON_MESSAGE( WM_FINDPATTERN, OnFindPattern ) ON_MESSAGE( WM_FETCHOBJECTLIST, OnP4ObjectFetch ) ON_MESSAGE( WM_GETOBJECTLIST, OnP4ObjectList ) ON_MESSAGE( WM_WIZFETCHOBJECTLIST, OnP4WizObjectFetch ) ON_MESSAGE( WM_WIZGETOBJECTLIST, OnP4WizObjectList ) ON_MESSAGE( WM_INTEGFETCHOBJECTLIST, OnP4IntegObjectFetch ) ON_MESSAGE( WM_INTEGGETOBJECTLIST, OnP4IntegObjectList ) ON_MESSAGE( WM_SELECTTHIS, OnSelectThis ) ON_MESSAGE( WM_ACTIVATEMODELESS, OnActivateModeless ) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CP4ListCtrl diagnostics #ifdef _DEBUG void CP4ListCtrl::AssertValid() const { CListCtrl::AssertValid(); } void CP4ListCtrl::Dump(CDumpContext& dc) const { CListCtrl::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CP4ListCtrl message handlers void CP4ListCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if(pNMListView->iItem == -1) { if(pNMListView->iSubItem == m_LastSortCol) m_SortAscending= !m_SortAscending; else m_LastSortCol=pNMListView->iSubItem; ReSort(); } *pResult = 0; } struct SortParam { CP4ListCtrl * listCtrl; int subItem; }; static int CALLBACK SortCallback(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { SortParam *sp = (SortParam*)lParamSort; int rc = sp->listCtrl->OnCompareItems(lParam1, lParam2, sp->subItem); // check for duplicate keys; if so, sort on next sort columns int nextcol; if (!rc && sp->subItem && ((nextcol = sp->listCtrl->NextSortColumn(sp->subItem)) != 0)) { // nextcol now contains a value like 1 for col-0-ascending or like -1 for col-0-decending BOOL saveSortAscending = sp->listCtrl->m_SortAscending; if (nextcol < 0) { nextcol = 0 - nextcol; sp->listCtrl->m_SortAscending = FALSE; } else sp->listCtrl->m_SortAscending = TRUE; rc = sp->listCtrl->OnCompareItems(lParam1, lParam2, (LPARAM)(nextcol - 1)); sp->listCtrl->m_SortAscending = saveSortAscending; } return rc; } void CP4ListCtrl::ReSort() { // update sort column and/or direction AddSortColumn(m_LastSortCol, m_SortAscending); // actually sort the list items SortParam sp; sp.listCtrl = this; sp.subItem = m_LastSortCol; SetRedraw(FALSE); SortItems(SortCallback, (DWORD)&sp); SetRedraw(TRUE); // update the header and make sure the selected item is visible m_headerctrl.SetSortImage( m_LastSortCol, m_SortAscending ); if( GetSelectedItem() != -1 ) EnsureVisible(GetSelectedItem(), FALSE); } BOOL CP4ListCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if(SERVER_BUSY()) return SET_BUSYCURSOR(); else return CListCtrl::OnSetCursor(pWnd, nHitTest, message); } void CP4ListCtrl::SetIndexAndPoint( int &index, CPoint &point ) { if( m_ContextContext == MOUSEHIT ) { index= GetContextItem( point ); m_ContextContext= KEYSTROKED; } else { CRect rect; GetClientRect(&rect); point= rect.CenterPoint(); ClientToScreen(&point); index= GetSelectedItem(); } } int CP4ListCtrl::GetContextItem( const CPoint & point ) { int index = GetHitItem ( point, TRUE ); if ( index < 0 ) { // user right clicked on the empty part of the pane. // if something is selected, unselect it so user doesn't get confused. // int i = GetSelectedItem( ); if ( i > -1 ) SetItemState( i, 0 , LVIS_SELECTED|LVIS_FOCUSED ); } else { // we got a hit on a name. select it, since another name may have // been selected -- if the user has right-clicked on a new name, // let's assume s/he wants this new one to be selected as well. // SetItemState( index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); } return index; } ////////////////////////////////////////////////////////////////////////////// // Windows NT3.51 screws up context menues in listview, just as NT4 screws these // up in tree views. Manually implement context menu invokation. void CP4ListCtrl::OnRButtonDown(UINT nFlags, CPoint point) { // Just eat the message } void CP4ListCtrl::OnRButtonUp(UINT nFlags, CPoint point) { // Right click context menu is broken in CListViews - a dbl right click is what // it wants. CPoint screenPt=point; ClientToScreen(&screenPt); m_ContextContext= MOUSEHIT; OnContextMenu(NULL, screenPt); } // Save current column widths to the registry void CP4ListCtrl::OnDestroy() { SaveColumnWidths(); CListCtrl::OnDestroy(); } // Save current column widths to the registry void CP4ListCtrl::SaveColumnWidths() { // Save the column widths if(m_ColsInited && !m_SubKey.IsEmpty()) { CString theKey = sRegKey + m_SubKey; CRegKeyEx key; if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, theKey)) { CString str; int i; for(i=0; i < MAX_P4OBJECTS_COLUMNS; i++) { // Note that GetColumnWidth returns zero if i > numcols CString num; num.Format(_T("%d"), GetColumnWidth(i)); if(i) str+=_T(","); str+=num; } key.SetValueString(str, sRegValue_ColumnWidths); str.Empty(); for(i=0; i < MAX_SORT_COLUMNS; i++) { CString num; num.Format(_T("%d"), m_SortColumns[i]); if(i) str+=_T(","); str+=num; } key.SetValueString(str, sRegValue_SortColumns); } } } // Check the registry to see if we have recorded the column widths last // used for this list view void CP4ListCtrl::RestoreSavedWidths(int *width, int numcols, LPCTSTR subkey) { m_SubKey = subkey; if(!m_SubKey.IsEmpty()) { CString theKey = sRegKey + m_SubKey; CRegKeyEx key; if(ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, theKey, KEY_READ)) { CString colWidths = key.QueryValueString(sRegValue_ColumnWidths); CString sortCols = key.QueryValueString(sRegValue_SortColumns); if(!colWidths.IsEmpty()) { // things can go wrong with the registry setting of the // widths. // use the defaults if the entry is all zeroes. // if ( colWidths != _T("0,0,0,0,0,0,0,0,0,0") ) for(int i=0; i< numcols; i++) { // Always use at least 30, so no column gets hidden. Very important // when changing p4port and hitting a server whose job spec has more // columns than the last server had. Allowing the width of new columns // to default to zero can confound someone unfamiliar with spreadsheet // operation, since it is not obvious that you can drag them back to a // visible width // // Also check that the column width has not been set too large // XP seems to lose it if the column width is very large - job006177. width[i] = GetPositiveNumber(colWidths); if (width[i] < 30) width[i] = 30; else if (width[i] > 3000) width[i] = 150; } } 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_LastSortCol=abs(m_SortColumns[0]) - 1; // SortColumns are signed & 1-relative m_SortAscending = m_SortColumns[0] >= 0 ? TRUE : FALSE; } } m_ReadSavedWidths= TRUE; } // Make sure no column completely disappeared (because you can't get it back then) for (int i=-1; ++i < numcols; ) { if (width[i] < 5) width[i] = 5; } } // Check the registry to see if we have recorded column names last // used for this list view BOOL CP4ListCtrl::GetSavedColumnNames(CStringArray &colNames, LPCTSTR subkey) { BOOL rc = TRUE; CString allCols = _T(":101 :102 :103 :104 :105 "); // note ' ' must follow last fieldname m_SubKey = subkey; CString theKey = sRegKey + m_SubKey; CRegKeyEx key; if(ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, theKey, KEY_READ)) { CString colNames = key.QueryValueString(sRegValue_ColumnNames); if(!colNames.IsEmpty()) { allCols = colNames; allCols += _T(' '); rc = FALSE; } } int i; int j = 0; while ((j++ < MAX_P4OBJECTS_COLUMNS) && ((i = allCols.Find(_T(' '))) != -1)) { CString name = allCols.Left(i); colNames.Add(name); allCols = allCols.Right(allCols.GetLength() - i); allCols.TrimLeft(); } return rc; } void CP4ListCtrl::SaveColumnNames(CString &colNames, LPCTSTR subkey) { // Save the column names if(!m_SubKey.IsEmpty()) { CString theKey = sRegKey + m_SubKey; CRegKeyEx key; if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, theKey)) { colNames.TrimRight(); key.SetValueString(colNames, sRegValue_ColumnNames); } } } void CP4ListCtrl::DeleteColumnNames(LPCTSTR subkey) { // Save the column names if(!m_SubKey.IsEmpty()) { CString theKey = sRegKey + m_SubKey; CRegKeyEx key; if(ERROR_SUCCESS == key.Create(HKEY_CURRENT_USER, theKey)) { key.DeleteValue(sRegValue_ColumnNames); } } } /* _________________________________________________________________ hitting enter should do what the left click button does (mainly call ondescribe, although some lists call onedit for some reason) do delete too, when i get the owners of the clients etc. and deletes are allowed. also, when i know who is a super user, allow inserts. _________________________________________________________________ */ void CP4ListCtrl::OnKeydown( NMHDR *pNMHDR, LRESULT *pResult) { LV_KEYDOWN *pLVKeyDow = ( LV_KEYDOWN *)pNMHDR; MainFrame()->SetGotUserInput( ); switch ( pLVKeyDow->wVKey ) { case VK_RETURN: OnDescribe( ); break; case VK_DELETE: OnDelete( GetDeleteType( ) ); break; case VK_TAB: if ( ::GetKeyState(VK_CONTROL) & 0x8000 ) // Ctrl+TAB switches to opposite pane MainFrame()->SwitchPanes(DYNAMIC_DOWNCAST(CView, GetParent()), ( ::GetKeyState(VK_SHIFT) & 0x8000 )); // Shift+Ctrl+TAB switches to status pane break; } *pResult = 0; } /* _________________________________________________________________ */ int CP4ListCtrl::GetDeleteType( ) { return ( m_viewType == P4BRANCH_SPEC ) ? P4BRANCH_DEL : ( m_viewType == P4CLIENT_SPEC ) ? P4CLIENT_DEL : ( m_viewType == P4JOB_SPEC ) ? P4JOB_DEL : ( m_viewType == P4LABEL_SPEC ) ? P4LABEL_DEL : ( m_viewType == P4USER_SPEC ) ? P4USER_DEL : 0; } /* _________________________________________________________________ put all this in one place to reduce code bloat. _________________________________________________________________ */ void CP4ListCtrl::InsertColumnHeaders( const CStringArray &colnames, int * widths ) { // Delete all of the old columns. int nColumnCount = GetHeaderCtrl()->GetItemCount(); for (int i=-1; ++i < nColumnCount; ) DeleteColumn(0); SetImageList( TheApp()->GetImageList(), LVSIL_SMALL ); for( int subItem = 0; subItem < colnames.GetSize(); subItem++ ) { LVCOLUMN lvCol; lvCol.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH; lvCol.fmt = LVCFMT_LEFT; lvCol.pszText = ( LPTSTR )( LPCTSTR ) colnames.GetAt( subItem ); lvCol.iSubItem = lvCol.iOrder = subItem; lvCol.cx = widths[ subItem ]; static BOOL bErrDispalyed=FALSE; int i = InsertColumn( subItem, &lvCol ); if (i==-1 && !bErrDispalyed) { AfxMessageBox(_T("Column header insert failure"), MB_ICONSTOP); bErrDispalyed = TRUE; } } m_ColsInited = TRUE; } /* _________________________________________________________________ get the string of the item. return it. if nothing is selected return an empty string. _________________________________________________________________ */ CString CP4ListCtrl::GetSelectedItemText( ) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; int index = GetSelectedItem ( ); CString name = ""; if( index != -1 ) { GetItemText( index, 0, str, LISTVIEWNAMEBUFSIZE ); name = str; } return name; } CString CP4ListCtrl::GetSelectedItemOwner(int ownerColumnNumber) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; int index = GetSelectedItem ( ); CString owner = ""; if( index != -1 ) { GetItemText( index, ownerColumnNumber, str, LISTVIEWNAMEBUFSIZE ); owner = str; } return owner; } BOOL CP4ListCtrl::SelectedItemIsLocked(int optionsColumnNumber ) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; int index = GetSelectedItem ( ); if( index != -1 ) { GetItemText( index, optionsColumnNumber, str, LISTVIEWNAMEBUFSIZE ); if (str[0] == _T('l')) return TRUE; } return FALSE; } int CP4ListCtrl::GetSelectedItem() { if ( m_LastSelIx != -1 && GetItemState( m_LastSelIx, LVIS_SELECTED ) == LVIS_SELECTED ) return m_LastSelIx; int cnt = GetItemCount(); int start = max(GetTopIndex() - 100, -1); for( m_LastSelIx = start; ++m_LastSelIx < cnt; ) { if( GetItemState( m_LastSelIx, LVIS_SELECTED ) == LVIS_SELECTED ) return m_LastSelIx; } for( m_LastSelIx = start+1; --m_LastSelIx >= 0; ) { if( GetItemState( m_LastSelIx, LVIS_SELECTED ) == LVIS_SELECTED ) return m_LastSelIx; } return m_LastSelIx = -1; } int CP4ListCtrl::GetHitItem( const CPoint &point, BOOL bScreenCoords /* = FALSE */) { CPoint local = point; if ( bScreenCoords ) ScreenToClient( &local ); LV_HITTESTINFO ht; ht.pt = local; ht.flags = LVHT_ONITEMICON | LVHT_ONITEMLABEL; return HitTest( &ht ); } int CP4ListCtrl::FindInList( const CString &name ) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; for( int i = 0; i < GetItemCount(); i++ ) { GetItemText( i, 0, str, LISTVIEWNAMEBUFSIZE ); if( !Compare(name,str) ) return i; } return -1; } int CP4ListCtrl::FindInListAll( const CString &name ) { int cnt = m_ListAll.column[0].GetCount(); for( int i = 0; i < cnt; i++ ) { if( !Compare(name, m_ListAll.column[0].GetAt(i)) ) return i; } return -1; } int CP4ListCtrl::FindInListNoCase( const CString &name ) { TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; for( int i = 0; i < GetItemCount(); i++ ) { GetItemText( i, 0, str, LISTVIEWNAMEBUFSIZE ); if( !name.CompareNoCase(str) ) return i; } return -1; } void CP4ListCtrl::OnDescribe( void ) { m_Active = GetSelectedItemText(); if ( m_Active.IsEmpty( ) ) return; CCmd_Describe *pCmd = new CCmd_Describe; pCmd->Init( m_hWnd, RUN_ASYNC ); if( pCmd->Run( m_viewType, m_Describing = m_Active ) ) { MainFrame()->UpdateStatus( LoadStringResource(IDS_FETCHING_SPEC) ); return; } else { delete pCmd; return; } } LRESULT CP4ListCtrl::OnP4Describe( WPARAM wParam, LPARAM lParam ) { MSG msg; BOOL ret = FALSE; CCmd_Describe *pCmd = ( CCmd_Describe * )wParam; if(!pCmd->GetError() && !MainFrame()->IsQuitting()) { CString ref = pCmd->GetReference(); ASSERT(!ref.IsEmpty()); if (!ref.IsEmpty()) m_Describing = ref; int i, j = -2; CString specblankdesc = CCmd_EditSpec::g_blankDesc; CString desc = MakeCRs( pCmd->GetDescription( ) ); 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(' ')))) { CString msg; msg.FormatMessage(IDS_THERE_IS_NO_s_TO_DESCRIBE, m_Describing); AddToStatus(msg, SV_WARNING); } else { int key; CSpecDescDlg *dlg = new CSpecDescDlg(this); dlg->SetIsModeless(TRUE); dlg->SetKey(key = pCmd->HaveServerLock()? pCmd->GetServerKey() : 0); if (m_viewType == P4USER_SPEC) { if ((desc.Find(_T("\r\n\r\nUpdate:\t")) == -1) && (desc.Find(_T("\r\n\r\nAccess:\t")) == -1)) { desc += _T("\n\n\tTHIS USER DOES NOT APPEAR TO EXIST - has it been deleted?"); } } dlg->SetDescription( desc ); dlg->SetItemName( m_Describing ); if (m_viewType == P4JOB_SPEC) dlg->SetReportedByTitle( m_ReportedByTitle ); CString caption; caption.FormatMessage(IDS_PERFORCE_DESCRIPTION_FOR_s, m_Describing); dlg->SetCaption( caption ); dlg->SetShowNextPrev(m_UpdateState == LIST_UPDATED ? TRUE : FALSE); dlg->SetShowShowFixes(m_viewType == P4JOB_SPEC); dlg->SetShowShowFiles(m_viewType == P4LABEL_SPEC); dlg->SetViewType(m_viewType); dlg->SetCaller(pCmd->GetCaller()); if (pCmd->GetListCtrl() && pCmd->GetListCtrl() != this) { dlg->SetShowEditBtn(FALSE); dlg->SetListCtrl(pCmd->GetListCtrl()); } else { dlg->SetListCtrl(this); switch(m_viewType) { case P4CLIENT_SPEC: dlg->SetShowEditBtn(!key && GET_P4REGPTR()->GetP4Client() == m_Describing); break; case P4USER_SPEC: dlg->SetShowEditBtn(!key && GET_P4REGPTR()->GetP4User() == m_Describing); break; default: dlg->SetShowEditBtn(!key ? TRUE : FALSE); break; } } if (m_EditInProgress) // overrides any previous possibility dlg->SetShowEditBtn(FALSE); 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 { EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE); dlg->DestroyWindow(); // some error! clean up delete dlg; } else ret = TRUE; } } else // had an error - need to turn painting back on { EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE); } delete pCmd; MainFrame()->ClearStatus(); return ret; } LRESULT CP4ListCtrl::OnP4EndDescribe( WPARAM wParam, LPARAM lParam ) { CSpecDescDlg *dlg = (CSpecDescDlg *)lParam; CString ref = dlg->GetItemName(); ASSERT(!ref.IsEmpty()); if (!ref.IsEmpty()) m_Describing = ref; switch(wParam) // which button did they click to close the box? { case IDC_NEXTITEM: case IDC_PREVITEM: { CListCtrl *plc = (CListCtrl *)dlg->GetListCtrl(); if (SetToNextPrevItem(m_Describing, wParam == IDC_NEXTITEM ? 1 : -1, plc)) { if (plc == this) OnDescribe(); // display the next/prev in the list on the screen else dlg->GetCaller()->SendMessage(WM_COMMAND, IDC_DESCRIBE, 0); break; } } case IDC_EDITIT: if (wParam == IDC_EDITIT) // note fall-thru from above! EditTheSpec(&m_Describing); default: // clicked OK, pressed ESC or ENTER - need to turn painting back on EnumChildWindows(AfxGetMainWnd()->m_hWnd, ChildSetRedraw, TRUE); break; } dlg->DestroyWindow(); return TRUE; } // The plc points to the list control that initially fired off // the Describe - it may not be the same as 'this' BOOL CP4ListCtrl::SetToNextPrevItem(CString& name, int np, CListCtrl *plc) { // Rummage through the list for the desired item int index = -1; int count = plc->GetItemCount(); TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; BOOL b = FALSE; if (plc == this && m_LastSelIx != -1) { plc->GetItemText( m_LastSelIx, 0, str, LISTVIEWNAMEBUFSIZE ); if( !Compare(name,str) ) { index = m_LastSelIx; b = TRUE; } } if (!b) { for( int i = -1; ++i < count; ) { plc->GetItemText( i, 0, str, LISTVIEWNAMEBUFSIZE ); if( !Compare(name,str) ) { index = i; break; } } } if((index + np < 0) || (index == -1) || (index + np >= count)) return FALSE; plc->SetItemState( index + np, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); if (b) m_LastSelIx = index + np; return TRUE; } void CP4ListCtrl::OnDelete( int type ) { m_Active = GetSelectedItemText( ) ; if ( m_Active.IsEmpty( ) ) return; if ( !OKToDelete( ) ) return; CString msg; msg.FormatMessage ( IDS_DELETEIT_s, m_Active ); if( AfxMessageBox( msg, MB_YESNO|MB_DEFBUTTON2|MB_ICONQUESTION ) != IDYES) return; CCmd_Delete *pCmd = new CCmd_Delete; pCmd->Init( m_hWnd, RUN_ASYNC ); if( pCmd->Run( type, m_Active ) ) MainFrame()->UpdateStatus( LoadStringResource(IDS_DELETING) ); else delete pCmd; } LRESULT CP4ListCtrl::OnP4Delete( WPARAM wParam, LPARAM lParam ) { CCmd_Delete *pCmd = ( CCmd_Delete * )wParam; if(!pCmd->GetError()) { AddToStatus( pCmd->GetCompletionMessage() , SV_COMPLETION ); int index = FindInList( m_Active ); if ( index > -1 ) DeleteItem( index ); index = FindInListAll( m_Active ); if ( index > -1 ) m_ListAll.column[0].SetAt(index, _T("@@")); // Removes from list of all items } // the following code is only used if a new user is created, // but then the edit of the new user is canceled. // in that case the newly created user is deleted // so we then want to switch back to the previous user. if ( m_viewType == P4USER_SPEC ) { CString sw2user = pCmd->GetSwitch2User(); if (!sw2user.IsEmpty()) { // switch to the previous user if( !GET_P4REGPTR()->SetP4User( sw2user, TRUE, FALSE, FALSE ) ) { AfxMessageBox( IDS_UNABLE_TO_WRITE_P4USER_TO_THE_REGISTRY, MB_ICONEXCLAMATION); m_Active = GET_P4REGPTR()->GetP4User(); } MainFrame()->UpdateCaption( ) ; } } delete pCmd; MainFrame()->ClearStatus(); return 0; } /* _________________________________________________________________ */ BOOL CP4ListCtrl::OnUpdateShowMenuItem( CCmdUI* pCmdUI, UINT idString ) { CString str; CString txt = GetSelectedItemText(); str.FormatMessage(idString, TruncateString(txt, 50)); pCmdUI->SetText ( str ); return( !SERVER_BUSY( ) && !txt.IsEmpty( ) ); } int CP4ListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CListCtrl::OnCreate(lpCreateStruct) == -1) return -1; DWORD dwStyle = ::SendMessage(m_hWnd,LVM_GETEXTENDEDLISTVIEWSTYLE,0,0); dwStyle |= LVS_EX_FULLROWSELECT; ::SendMessage(m_hWnd,LVM_SETEXTENDEDLISTVIEWSTYLE,0,dwStyle); // In any event, subclass the stinkin header so we can owner-draw // sort arrows to indicate how its sorted m_headerctrl.SubclassWindow(GetHeaderCtrl()->m_hWnd); return 0; } void CP4ListCtrl::OnUpdateEditCopy(CCmdUI* pCmdUI) { pCmdUI->Enable(GetItemCount() ? TRUE : FALSE); } void CP4ListCtrl::OnEditCopy() { CString txt = GetSelectedItemText(); CopyTextToClipboard(txt); } void CP4ListCtrl::OnEditPaste( const CString &name ) { int index; if (m_viewType == P4JOB_SPEC && _istdigit(name.GetAt(0)) && GET_P4REGPTR()->GetConvertJobNbr()) { BOOL b = TRUE; CString jobStr = name; for (int i=0; ++i < jobStr.GetLength(); ) { if (!_istdigit(jobStr.GetAt(i))) { b = FALSE; break; } } if (b) jobStr.FormatMessage(IDS_JOBNAME_FORMAT_n, _tstoi(jobStr)); index = FindInList( jobStr ); } else index = FindInList( name ); if ( index > -1 ) { SetItemState( index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); EnsureVisible(index, FALSE); } else MessageBeep(0); } // Save sort columns in array of signed, 1-relative numbers; // negative columns numbers are for descending, positive for ascending. // Note that incoming 'colnbr' is a non-negative 0-relative number and must be incremented void CP4ListCtrl::AddSortColumn(int colnbr, BOOL sortAscending) { int i; BOOL lastAsc = m_SortColumns[0] >= 0 ? TRUE : FALSE; // first, check for just changing ascending/descending (after making 1 relative) if (++colnbr == abs(m_SortColumns[0])) { if (sortAscending != lastAsc) m_SortColumns[0] = 0 - m_SortColumns[0]; return; } // if first column, clear the saved columns since the 1st column is unique if (colnbr == 1) { for (i = -1; ++i < MAX_SORT_COLUMNS; ) m_SortColumns[i] = 0; } // descending columns are stored as a negative number if (!sortAscending) colnbr = 0 - colnbr; // move older columns up one, then save new signed, 1-relative column number for (i = MAX_SORT_COLUMNS; --i; ) m_SortColumns[i] = m_SortColumns[i-1]; m_SortColumns[0] = colnbr; // remove any older references to this column since they are no longer valid for (i = 0; ++i < MAX_SORT_COLUMNS; ) { if (!m_SortColumns[i]) break; if (abs(m_SortColumns[i]) == abs(colnbr)) { for (int j = i; ++j < MAX_SORT_COLUMNS; ) m_SortColumns[j-1] = m_SortColumns[j]; m_SortColumns[MAX_SORT_COLUMNS-1] = 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 CP4ListCtrl::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 } LRESULT CP4ListCtrl::OnFindPattern(WPARAM wParam, LPARAM lParam) { TCHAR str[ 1024 ]; CString text; CString what = (TCHAR *)lParam; int flags = (int)wParam; if (!(flags & FR_MATCHCASE)) what.MakeLower(); int i = GetSelectedItem( ); int j; if ( i == -1 ) SetItemState( 0, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); int columns = GetHeaderCtrl()->GetItemCount(); if (flags & FR_DOWN) { if (flags < 0) i++; while( i < GetItemCount() ) { for (j =-1; ++j < columns; ) { GetItemText( i, j, str, sizeof(str)/sizeof(TCHAR) ); text = str; if (!(flags & FR_MATCHCASE)) text.MakeLower(); if (text.Find(what) != -1) { SetItemState( i, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); EnsureVisible(i, FALSE); MainFrame()->SetMessageText(LoadStringResource(IDS_FOUND)); return 0; } } i++; } } else { if (flags < 0) i--; while( i >= 0 ) { for (j =-1; ++j < columns; ) { GetItemText( i, j, str, sizeof(str)/sizeof(TCHAR) ); text = str; if (!(flags & FR_MATCHCASE)) text.MakeLower(); if (text.Find(what) != -1) { SetItemState( i, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); EnsureVisible(i, FALSE); MainFrame()->SetMessageText(LoadStringResource(IDS_FOUND)); return 0; } } i--; } } MessageBeep(0); MainFrame()->SetMessageText(LoadStringResource(IDS_NOT_FOUND)); return 0; } void CP4ListCtrl::OnLButtonDown(UINT nFlags, CPoint point) { // find out what was hit LVHITTESTINFO ht; ht.pt=point; int index = HitTest( &ht ); // if it wasn't on an item, return if( index == -1 || !(ht.flags & LVHT_ONITEM) ) return; // We can't call CListView::OnLButtonDown(nFlags, point) like we want to // because doing so screws up the double click (you have to double click slowly // for it to be detected(!)), so we have to set the selection ourselves. SetItemState(index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); EnsureVisible(index, FALSE); TCHAR str[ LISTVIEWNAMEBUFSIZE + 1 ]; GetItemText( index, 0, str, LISTVIEWNAMEBUFSIZE ); m_DragFromItemName = str; // Create a suitably small drag rect around the cursor CPoint pt= point; ClientToScreen(&pt); m_DragSourceRect.SetRect( max(0, pt.x - 2), max(0, pt.y - 2), max(0, pt.x + 2), max(0, pt.y + 2) ); TryDragDrop( ); } BOOL CP4ListCtrl::PreCreateWindow(CREATESTRUCT& cs) { cs.style|=LVS_SINGLESEL | LVS_SHAREIMAGELISTS | LVS_ALIGNLEFT | LVS_REPORT; if (GET_P4REGPTR( )->AlwaysShowFocus()) cs.style|=LVS_SHOWSELALWAYS; return CListCtrl::PreCreateWindow(cs); } void CP4ListCtrl::GetListItems(CStringArray *list) { for(int i = 0; i < GetItemCount(); i++) list->Add(GetItemText(i, 0)); } LRESULT CP4ListCtrl::OnP4ObjectFetch( WPARAM wParam, LPARAM lParam ) { if( GetItemCount() > 0 ) SendMessage( WM_GETOBJECTLIST, wParam, lParam); else { m_PostViewUpdateMsg = WM_GETOBJECTLIST; m_PostViewUpdateWParam = wParam; m_PostViewUpdateLParam = lParam; ViewUpdate(); } return 0; } LRESULT CP4ListCtrl::OnP4IntegObjectFetch( WPARAM wParam, LPARAM lParam ) { if( GetItemCount() > 0 ) SendMessage( WM_INTEGGETOBJECTLIST, wParam, lParam); else { m_PostViewUpdateMsg = WM_INTEGGETOBJECTLIST; m_PostViewUpdateWParam = wParam; m_PostViewUpdateLParam = lParam; ViewUpdate(); } return 0; } LRESULT CP4ListCtrl::OnP4WizObjectFetch( WPARAM wParam, LPARAM lParam ) { if( GetItemCount() > 0 ) SendMessage( WM_WIZGETOBJECTLIST, wParam, lParam); else { // Wizard doesn't work if there are not any clients in the client pane // So simulate pressing the Back button CString txt; CP4PaneView * pv = DYNAMIC_DOWNCAST(CP4PaneView, GetWnd()->GetParent()); CString caption = pv ? pv->GetContent()->GetCaption() : _T("Perforce Objects"); txt.FormatMessage(IDS_NO_s_AVAILABLE, caption); AfxMessageBox(txt, MB_ICONEXCLAMATION); HWND hWnd = (HWND)wParam; UINT msg = (UINT)lParam; ::SendMessage(hWnd, msg, IDC_BACK, 0); } return 0; } int CP4ListCtrl::GetColNamesAndCount(CStringArray &cols) { TCHAR txt[100]; CString str; int nbrcols; LVCOLUMN column; column.mask = LVCF_TEXT | LVCF_SUBITEM; column.pszText = txt; column.cchTextMax = sizeof(txt)/sizeof(TCHAR)-1; for (nbrcols = -1; ++nbrcols < MAX_P4OBJECTS_COLUMNS; ) { if (GetColumn(nbrcols, &column)) { str = column.pszText; cols.Add(str); } else break; } return nbrcols; } LRESULT CP4ListCtrl::OnSelectThis(WPARAM wParam, LPARAM lParam) { CString *name = (CString *)lParam; int index = FindInList( *name ); if ( index > -1 ) { SetItemState( index, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED ); EnsureVisible(index, FALSE); } return 0; } LRESULT CP4ListCtrl::OnP4ObjectList(WPARAM wParam, LPARAM lParam) { return P4ObjectList(wParam, lParam, FALSE, FALSE); } LRESULT CP4ListCtrl::OnP4WizObjectList(WPARAM wParam, LPARAM lParam) { return P4ObjectList(wParam, lParam, TRUE, FALSE); } LRESULT CP4ListCtrl::OnP4IntegObjectList(WPARAM wParam, LPARAM lParam) { return P4ObjectList(wParam, lParam, FALSE, TRUE); } LRESULT CP4ListCtrl::P4ObjectList(WPARAM wParam, LPARAM lParam, BOOL bWiz, BOOL bInteg, BOOL bFiltered) { int i; CString str; CString caption; CP4PaneView * pv = DYNAMIC_DOWNCAST(CP4PaneView, GetWnd()->GetParent()); if (bWiz && pv) caption = pv->GetContent()->GetPlainCaption() + LoadStringResource(IDS__WIZARD); else caption = pv ? pv->GetContent()->GetCaption() : _T("Perforce Objects"); // Get the column names and count CStringArray cols; int nbrcols = GetColNamesAndCount(cols); // Get the list of objects SET_APP_HALTED(TRUE); CObList *objs = new CObList; if (bFiltered) { for(i = 0; i < GetItemCount(); i++ ) { int subitem; CP4Object *newObj= new CP4Object(); for (subitem = -1; ++subitem < nbrcols; ) { str = GetItemText(i, subitem); if (!subitem) newObj->Create(str); else newObj->AddField(str); } objs->AddHead(newObj); } } else { if (pv) { caption = pv->GetContent()->GetPlainCaption(); if (bWiz) caption += LoadStringResource(IDS__WIZARD); } for(i = 0; i < m_ListAll.column[0].GetCount(); i++ ) { int subitem; CP4Object *newObj= new CP4Object(); for (subitem = -1; ++subitem < nbrcols; ) { str = m_ListAll.column[subitem].GetAt(i); if (!subitem) newObj->Create(str); else newObj->AddField(str); } objs->AddHead(newObj); } } if( objs->GetCount() == 0 ) { CString txt; txt.FormatMessage(IDS_NO_s_AVAILABLE, caption); AfxMessageBox(txt, MB_ICONEXCLAMATION); SET_APP_HALTED(FALSE); delete objs; return 0; } // Get the currently selected item's name i = GetSelectedItem(); CString curr = GetItemText(i, 0); // Display the dialog box. CP4ListBrowse dlg(this, bWiz, bInteg); dlg.SetP4ObjectFont(GetFont()); dlg.SetP4ObjectType(m_viewType); dlg.SetP4ObjectList(objs); dlg.SetP4ObjectCols(&cols); dlg.SetP4ObjectCurr(&curr); dlg.SetP4ObjectSKey(&m_SubKey); dlg.SetP4ObjectCaption(&caption); dlg.SetP4ObjectImage(m_iImage); if (bFiltered && (GetItemCount() < m_ListAll.column[0].GetCount())) dlg.SetP4ObjectIsFiltered(TRUE); int retcode= dlg.DoModal(); SET_APP_HALTED(FALSE); // Delete the object list for(POSITION pos=objs->GetHeadPosition(); pos!=NULL; ) delete (CP4Object *) objs->GetNext(pos); delete objs; CString *objname= dlg.GetSelectedP4Object(); if(retcode == IDOK && !objname->IsEmpty()) { HWND hWnd = (HWND)wParam; UINT msg = (UINT)lParam; retcode = ::SendMessage(hWnd, msg, 0, (LPARAM)objname); } else if (retcode == IDC_REFRESH) { if (bFiltered && (GetItemCount() < m_ListAll.column[0].GetCount())) { retcode = P4ObjectList(wParam, lParam, bWiz, bInteg, FALSE); } else { m_PostViewUpdateMsg = dlg.IsBranchInteg() ? WM_INTEGFETCHOBJECTLIST : WM_GETOBJECTLIST; m_PostViewUpdateWParam = wParam; m_PostViewUpdateLParam = lParam; ViewUpdate(); retcode = 0; } } else if (retcode == IDC_BACK) { HWND hWnd = (HWND)wParam; UINT msg = (UINT)lParam; retcode = ::SendMessage(hWnd, msg, IDC_BACK, 0); } MainFrame()->ClearStatus(); return retcode; } void CP4ListCtrl::CantEditRightNow(int type) { CString msg; msg.FormatMessage(IDS_CANTEDIT_INPROGRESS, LoadStringResource(type), LoadStringResource(type)); AddToStatus( msg, SV_WARNING ); }