555 lines
15 KiB
C++
555 lines
15 KiB
C++
/*****************************************************************************
|
|
*
|
|
* 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);
|
|
}
|