/***************************************************************************** * * describe.cpp * * View a changelist a description. * *****************************************************************************/ #include "sdview.h" /***************************************************************************** * * class CDescribe * *****************************************************************************/ // // The LPARAM of the listview item has the following form: // // HIWORD = enum CATEGORY // LOWORD = original index (to break ties during sorting) // enum CATEGORY { CAT_HEADER, // changelist header CAT_MATCHED, // file that matches the pattern CAT_BLANK1, // separates matched from unmatched CAT_UNMATCHED, // files that don't match the pattern CAT_BLANK2, // separates unmatched from unchanged CAT_UNCHANGED, // unmatched files that weren't change }; class CDescribe : public LVFrame, public BGTask { friend DWORD CALLBACK CDescribe_ThreadProc(LPVOID lpParameter); protected: LRESULT HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam); private: enum { DM_RECALC = WM_APP }; typedef LVFrame super; LRESULT ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_WM_SIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_WM_SETCURSOR(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_LM_ITEMACTIVATE(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_LM_GETCONTEXTMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_LM_COPYTOCLIPBOARD(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_DM_RECALC(UINT uiMsg, WPARAM wParam, LPARAM lParam); private: /* Helpers */ CDescribe() { SetAcceleratorTable(MAKEINTRESOURCE(IDA_DESCRIBE)); } void _ResetChildWidth(); void _AdjustMenu(HMENU hmenu, int iItem, BOOL fContextMenu); LPTSTR _GetSanitizedLine(int iItem, LPTSTR pszBuf, UINT cch); void ViewOneFile(); void ViewFileLog(); int _GetBugNumber(int iItem, BOOL fContextMenu); static LPTSTR _SanitizeClipboardText(LPTSTR psz); static DWORD CALLBACK s_BGInvoke(LPVOID lpParam); DWORD _BGInvoke(); private: int _cxMax; int _iBug; Substring _ssChange; }; LRESULT CDescribe::ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam) { static const LVFCOLUMN s_rgcol[] = { { 30 ,IDS_COL_COMMENT ,LVCFMT_LEFT }, { 0 ,0 ,0 }, }; LRESULT lres; if (Parse(TEXT("$d"), _pszQuery, &_ssChange)) { String str; str << TEXT("sdv describe ") << _ssChange; SetWindowText(_hwnd, str); lres = super::HandleMessage(uiMsg, wParam, lParam); if (lres == 0 && SetWindowMenu(MAKEINTRESOURCE(IDM_DESCRIBE)) && CreateChild(LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER, LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT) && AddColumns(s_rgcol) && (SetWindowRedraw(_hwndChild, FALSE), TRUE) && BGStartTask(s_BGInvoke, this)) { } else { lres = -1; } } else { Help(_hwnd, TEXT("#descr")); lres = -1; } return lres; } LRESULT CDescribe::ON_WM_SETCURSOR(UINT uiMsg, WPARAM wParam, LPARAM lParam) { return BGFilterSetCursor(super::HandleMessage(uiMsg, wParam, lParam)); } LPTSTR CDescribe::_GetSanitizedLine(int iItem, LPTSTR pszBuf, UINT cch) { LPTSTR pszPath = NULL; if (iItem >= 0 && ListView_GetItemText(_hwndChild, iItem, pszBuf, cch)) { LPTSTR psz =_SanitizeClipboardText(pszBuf); if (psz != pszBuf) { pszPath = psz; } } return pszPath; } void CDescribe::ViewOneFile() { TCHAR sz[MAX_PATH]; LPTSTR pszPath = _GetSanitizedLine(GetCurSel(), sz, ARRAYSIZE(sz)); if (pszPath) { WindiffOneChange(pszPath); } } void CDescribe::ViewFileLog() { TCHAR sz[MAX_PATH]; LPTSTR pszPath = _GetSanitizedLine(GetCurSel(), sz, ARRAYSIZE(sz)); if (pszPath) { String str; LPTSTR pszSharp = StrChr(pszPath, TEXT('#')); if (pszSharp) { *pszSharp++ = TEXT('\0'); str << TEXT("-#") << pszSharp << TEXT(' '); } str << pszPath; LaunchThreadTask(CFileLog_ThreadProc, str); } } LRESULT CDescribe::ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam) { int iChange, iBug; switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDM_VIEWFILEDIFF: ViewOneFile(); return 0; case IDM_VIEWWINDIFF: WindiffChangelist(StrToInt(_ssChange._pszMin)); return 0; case IDM_VIEWBUG: iBug = _GetBugNumber(GetCurSel(), FALSE); if (iBug) { OpenBugWindow(_hwnd, iBug); } break; case IDM_VIEWFILELOG: ViewFileLog(); break; } return super::HandleMessage(uiMsg, wParam, lParam); } // // Execute the default context menu item. // LRESULT CDescribe::ON_LM_ITEMACTIVATE(UINT uiMsg, WPARAM wParam, LPARAM lParam) { HMENU hmenu = RECAST(HMENU, ON_LM_GETCONTEXTMENU(LM_GETCONTEXTMENU, wParam, 0)); if (hmenu) { FORWARD_WM_COMMAND(_hwnd, GetMenuItemID(hmenu, 0), NULL, 0, SendMessage); DestroyMenu(hmenu); } return 0; } int CDescribe::_GetBugNumber(int iItem, BOOL fContextMenu) { LPARAM lParam = RECAST(LPARAM, GetLVItem(iItem)); int iBug = 0; if (HIWORD(lParam) == CAT_HEADER && iItem != 0) { iBug = ParseBugNumberFromSubItem(_hwndChild, iItem, 0); } // If no bug number on the selection, use the default bug number // for this changelist. if (iBug == 0 && !fContextMenu) { iBug = _iBug; } return iBug; } void CDescribe::_AdjustMenu(HMENU hmenu, int iItem, BOOL fContextMenu) { TCHAR sz[MAX_PATH]; sz[0] = TEXT('\0'); if (iItem >= 0) { ListView_GetItemText(_hwndChild, iItem, sz, ARRAYSIZE(sz)); } // // Disable IDM_VIEWFILEDIFF and IDM_VIEWFILELOG // if this is not a "..." item. // BOOL fEnable = (Parse(TEXT("... "), sz, NULL) != NULL); EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILEDIFF, fEnable, fContextMenu); EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILELOG, fEnable, fContextMenu); // // If a context menu, then nuke IDM_VIEWWINDIFF if this is not // the "Change" item. // if (fContextMenu && iItem != 0) { DeleteMenu(hmenu, IDM_VIEWWINDIFF, MF_BYCOMMAND); } AdjustBugMenu(hmenu, _GetBugNumber(iItem, fContextMenu), fContextMenu); MakeMenuPretty(hmenu); } LRESULT CDescribe::ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam) { _AdjustMenu(RECAST(HMENU, wParam), GetCurSel(), FALSE); return 0; } LRESULT CDescribe::ON_LM_GETCONTEXTMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam) { HMENU hmenu = LoadPopupMenu(MAKEINTRESOURCE(IDM_DESCRIBE_POPUP)); if (hmenu) { _AdjustMenu(hmenu, (int)wParam, TRUE); } return RECAST(LRESULT, hmenu); } // // If the line begins "...", then strip off everything except for the // depot specification. // LPTSTR CDescribe::_SanitizeClipboardText(LPTSTR psz) { Substring rgss[2]; if (Parse(TEXT("... $P#$d"), psz, rgss)) { *(rgss[1]._pszMax) = TEXT('\0'); return rgss[0].Start(); } else { return psz; } } LRESULT CDescribe::ON_LM_COPYTOCLIPBOARD(UINT uiMsg, WPARAM wParam, LPARAM lParam) { String str; TCHAR sz[MAX_PATH]; int iMin = (int)wParam; int iMax = (int)lParam; // If a single-line copy, then special rules apply if (iMin + 1 == iMax) { if (ListView_GetItemText(_hwndChild, iMin, sz, ARRAYSIZE(sz))) { str << _SanitizeClipboardText(sz); } } else { for (int iItem = iMin; iItem < iMax; iItem++) { if (ListView_GetItemText(_hwndChild, iItem, sz, ARRAYSIZE(sz))) { str << sz; } str << TEXT("\r\n"); } } SetClipboardText(_hwnd, str); return 0; } LRESULT CDescribe::ON_DM_RECALC(UINT uiMsg, WPARAM wParam, LPARAM lParam) { ListView_SetColumnWidth(_hwndChild, 0, LVSCW_AUTOSIZE); _cxMax = ListView_GetColumnWidth(_hwndChild, 0); _ResetChildWidth(); LVFINDINFO lvfi; lvfi.flags = LVFI_PARTIAL; lvfi.psz = TEXT("..."); int iFirst = ListView_FindItem(_hwndChild, -1, &lvfi); if (iFirst >= 0) { ListView_SetCurSel(_hwndChild, iFirst); } SetWindowRedraw(_hwndChild, TRUE); return 0; } void CDescribe::_ResetChildWidth() { RECT rc; GetClientRect(_hwndChild, &rc); int cxMargins = GetSystemMetrics(SM_CXEDGE) * 2; int cxCol = max(_cxMax + cxMargins, rc.right); ListView_SetColumnWidth(_hwndChild, 0, cxCol); } LRESULT CDescribe::ON_WM_SIZE(UINT uiMsg, WPARAM wParam, LPARAM lParam) { super::HandleMessage(uiMsg, wParam, lParam); if (_cxMax) { _ResetChildWidth(); } return 0; } LRESULT CDescribe::HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam) { switch (uiMsg) { FW_MSG(WM_CREATE); FW_MSG(WM_SETCURSOR); FW_MSG(WM_COMMAND); FW_MSG(WM_INITMENU); FW_MSG(WM_SIZE); FW_MSG(LM_ITEMACTIVATE); FW_MSG(LM_GETCONTEXTMENU); FW_MSG(LM_COPYTOCLIPBOARD); FW_MSG(DM_RECALC); } return super::HandleMessage(uiMsg, wParam, lParam); } // // A private helper class that captures the parsing state machine. // class DescribeParseState { enum PHASE { PHASE_HEADERS, // collecting the header PHASE_FILES, // collecting the files PHASE_DIFFS, // collecting the diffs }; public: DescribeParseState(HWND hwndChild, LPCTSTR pszPattern) : _m(pszPattern) , _hwndChild(hwndChild) , _iPhase(PHASE_HEADERS) , _iLine(0) , _iMatch(-1) , _iBug(0) , _fAnyMatch(FALSE) { } void AddLine(LPTSTR psz, int iCat) { LVITEM lvi; lvi.mask = LVIF_TEXT | LVIF_PARAM; lvi.iItem = _iLine; lvi.iSubItem = 0; lvi.pszText = psz; lvi.lParam = MAKELONG(_iLine, iCat); _iLine++; ChangeTabsToSpaces(psz); ListView_InsertItem(_hwndChild, &lvi); } void SetMatchLine(LPTSTR psz) { // Turn the "====" into "..." so we can search for it LPTSTR pszDots = psz+1; pszDots[0] = TEXT('.'); pszDots[1] = TEXT('.'); pszDots[2] = TEXT('.'); LPTSTR pszSharp = StrChr(pszDots, TEXT('#')); if (!pszSharp) return; pszSharp[1] = TEXT('\0'); // this wipes out the thing after the '#' LVFINDINFO lvfi; lvfi.flags = LVFI_PARTIAL; lvfi.psz = pszDots; _iMatch = ListView_FindItem(_hwndChild, 0, &lvfi); if (_iMatch >= 0) { _cAdded = _cDeleted = 0; } } void FlushMatch() { if (_iMatch >= 0) { String str; str.Grow(MAX_PATH-1); LVITEM lvi; lvi.iItem = _iMatch; lvi.mask = LVIF_TEXT | LVIF_PARAM; lvi.iSubItem = 0; lvi.pszText = str; lvi.cchTextMax = str.BufferLength(); if (ListView_GetItem(_hwndChild, &lvi)) { str.SetLength(lstrlen(str)); str << TEXT(" (") << _cDeleted << TEXT('/') << _cAdded << TEXT(")"); lvi.pszText = str; if (_cDeleted + _cAdded == 0) { lvi.lParam = MAKELONG(LOWORD(lvi.lParam), CAT_UNCHANGED); } ListView_SetItem(_hwndChild, &lvi); } _iMatch = -1; } } void ParseLine(String& str) { Substring rgss[3]; switch (_iPhase) { case PHASE_HEADERS: if (Parse(TEXT("... "), str, NULL)) { _iPhase = PHASE_FILES; goto L_PHASE_FILES; } if (_iBug == 0 && str[0] == TEXT('\t')) { _iBug = ParseBugNumber(str); } AddLine(str, CAT_HEADER); break; case PHASE_FILES: if (Parse(TEXT("... "), str, NULL)) { L_PHASE_FILES: int iCat; if (_m.Matches(str + 4)) { _fAnyMatch = TRUE; iCat = CAT_MATCHED; } else { iCat = CAT_UNMATCHED; } AddLine(str, iCat); } else { _iPhase = PHASE_DIFFS; } break; case PHASE_DIFFS: if (Parse(TEXT("==== "), str, NULL)) { SetMatchLine(str); } else if (Parse(TEXT("add $d chunks $d lines"), str, rgss)) { _cAdded += StrToInt(rgss[1].Finalize()); } else if (Parse(TEXT("deleted $d chunks $d lines"), str, rgss)) { _cDeleted += StrToInt(rgss[1].Finalize()); } else if (Parse(TEXT("changed $d chunks $d / $d lines"), str, rgss)) { _cDeleted += StrToInt(rgss[1].Finalize()); _cAdded += StrToInt(rgss[2].Finalize()); FlushMatch(); } break; } } int Finish() { if (_fAnyMatch) { AddLine(TEXT(""), CAT_BLANK1); } AddLine(TEXT(""), CAT_BLANK2); ListView_SortItems(_hwndChild, s_Compare, 0); return _iBug; } static int CALLBACK s_Compare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { return (int)(lParam1 - lParam2); } private: Match _m; HWND _hwndChild; int _iPhase; int _iLine; int _iMatch; int _iBug; int _cAdded; int _cDeleted; BOOL _fAnyMatch; }; DWORD CALLBACK CDescribe::s_BGInvoke(LPVOID lpParam) { CDescribe *self = RECAST(CDescribe *, lpParam); return self->_BGInvoke(); } DWORD CDescribe::_BGInvoke() { DescribeParseState state(_hwndChild, _ssChange._pszMax); String str; str << TEXT("describe "); if (GlobalSettings.IsChurnEnabled()) { str << TEXT("-ds "); } else { str << TEXT("-s "); } str << _ssChange; SDChildProcess proc(str); IOBuffer buf(proc.Handle()); while (buf.NextLine(str)) { str.Chomp(); state.ParseLine(str); } _iBug = state.Finish(); PostMessage(_hwnd, DM_RECALC, 0, 0); BGEndTask(); return 0; } DWORD CALLBACK CDescribe_ThreadProc(LPVOID lpParameter) { return FrameWindow::RunThread(new CDescribe, lpParameter); }