|
|
/*****************************************************************************
* * opened.cpp * * View the list of opened files and pending changes. * *****************************************************************************/
#include "sdview.h"
/*****************************************************************************
* * OpenedEntry * * We list the changes in descending numerical order, except that * "default" goes at the top of the list (rather than at the bottom, * which is what StrToInt would've given us). * *****************************************************************************/
MakeStringFormat(ChangeList) MakeStringFormat(PendingOp)
class OpenedEntry : public TreeItem {
public: OpenedEntry(ChangeList clChange, LPCTSTR pszComment); OpenedEntry(PendingOp opOp, LPCTSTR pszFile); OpenedEntry() { }
void SetComment(LPCTSTR pszComment) { _scComment = pszComment; } void SetFullDescription(LPCTSTR pszFullDescription) { _scFullDescription = pszFullDescription; }
LRESULT GetDispInfo(NMTREELIST *pdi, int iColumn); LRESULT GetInfoTip(NMTREELIST *pdi);
LPCTSTR GetChange() const { return _scChange; } LPCTSTR GetComment() const { return _scComment; } int GetOp() const { return _iOp; } UINT GetSortKey() const { return _uiSort; } BOOL IsAddLike() const { return _iOp == OP_ADD || _iOp == OP_BRANCH; } BOOL IsDelLike() const { return _iOp == OP_DELETE; } BOOL HasComment() const { return !_scComment.IsEmpty(); }
static UINT ComputeSortKey(LPCTSTR pszChange) { return (UINT)StrToInt(pszChange) - 1; }
static UINT SortKey_DefaultChange() { return (UINT)0-1; }
private: void GetImage(NMTREELIST *ptl); private: UINT _uiSort; // Sort key
int _iOp; // Checkin operation
StringCache _scChange; // Change number or operation
StringCache _scComment; // Checkin comment or path
StringCache _scFullDescription; // Full checkin description
};
OpenedEntry::OpenedEntry(ChangeList clChange, LPCTSTR pszComment) : _scChange(clChange) , _uiSort(ComputeSortKey(clChange)) , _iOp(OP_EDIT) , _scComment(pszComment) { }
OpenedEntry::OpenedEntry(PendingOp opOp, LPCTSTR pszComment) : _scChange(opOp) , _iOp(ParseOp(opOp)) , _scComment(pszComment) { }
void OpenedEntry::GetImage(NMTREELIST *ptl) { if (_iOp > 0) { ptl->iSubItem = c_rgleim[_iOp]._iImage; } else { ptl->iSubItem = 0; } }
LRESULT OpenedEntry::GetDispInfo(NMTREELIST *ptl, int iColumn) { switch (iColumn) { case -1: GetImage(ptl); break; case 0: ptl->pszText = _scChange; break; case 1: ptl->pszText = _scComment; break; } return 0; }
LRESULT OpenedEntry::GetInfoTip(NMTREELIST *ptl) { ptl->pszText = _scFullDescription; return 0; }
/*****************************************************************************
* * class COpened * *****************************************************************************/
class COpened : public TLFrame, public BGTask {
friend DWORD CALLBACK COpened_ThreadProc(LPVOID lpParameter);
protected: LRESULT HandleMessage(UINT uiMsg, WPARAM wParam, LPARAM lParam);
private:
enum { OM_INITIALIZED = WM_APP };
typedef TLFrame super;
LRESULT ON_WM_CREATE(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_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam); LRESULT ON_OM_INITIALIZED(UINT uiMsg, WPARAM wParam, LPARAM lParam);
private: /* Helpers */ COpened() : TLFrame(new OpenedEntry) { SetAcceleratorTable(MAKEINTRESOURCE(IDA_OPENED)); }
OpenedEntry *OEGetCurSel() { return SAFECAST(OpenedEntry*, TLGetCurSel()); }
LRESULT _FillChildren(OpenedEntry *pleRoot, LPCTSTR pszRootPath); LRESULT _OnItemActivate(OpenedEntry *ple); LRESULT _OnGetContextMenu(OpenedEntry *ple);
BOOL _IsViewFileLogEnabled(OpenedEntry *ple); LRESULT _ViewFileLog(OpenedEntry *ple); void _AdjustMenu(HMENU hmenu, OpenedEntry *ple, BOOL fContextMenu);
int _GetChangeNumber(OpenedEntry *ple); int _GetBugNumber(OpenedEntry *ple);
static DWORD CALLBACK s_BGInvoke(LPVOID lpParam); DWORD _BGInvoke(); LPCTSTR _BGParse(StringCache *pscUser); void _BGGetChanges(LPCTSTR pszUser); void _BGFillInChanges(); OpenedEntry *_BGFindChange(LPCTSTR pszChange, BOOL fCreate); void _BGGetOpened(LPCTSTR pszArgs, LPCTSTR pszUser);
BOOL _IsChangeHeader(OpenedEntry *ple) { return ple && ple->Parent() == _tree.GetRoot(); }
BOOL _IsChangeFile(OpenedEntry *ple) { return ple && ple->Parent() != _tree.GetRoot(); }
private: };
LRESULT COpened::ON_WM_CREATE(UINT uiMsg, WPARAM wParam, LPARAM lParam) { LRESULT lres;
static const LVFCOLUMN c_rgcol[] = { { 15 ,IDS_COL_CHANGE ,LVCFMT_LEFT }, { 60 ,IDS_COL_COMMENT ,LVCFMT_LEFT }, { 0 ,0 ,0 }, };
lres = super::HandleMessage(uiMsg, wParam, lParam); if (lres == 0 && _tree.GetRoot() && SetWindowMenu(MAKEINTRESOURCE(IDM_OPENED)) && CreateChild(LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER, LVS_EX_LABELTIP | LVS_EX_HEADERDRAGDROP | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT) && AddColumns(c_rgcol) && BGStartTask(s_BGInvoke, this)) { SetWindowText(_hwnd, TEXT("sdv opened")); } else { lres = -1; } return lres; }
int COpened::_GetBugNumber(OpenedEntry *ple) { if (_IsChangeFile(ple)) { ple = SAFECAST(OpenedEntry *, ple->Parent()); }
if (ple) { return ParseBugNumber(ple->GetComment()); } else { return 0; } }
int COpened::_GetChangeNumber(OpenedEntry *ple) { if (_IsChangeFile(ple)) { ple = SAFECAST(OpenedEntry *, ple->Parent()); }
if (ple) { return StrToInt(ple->GetChange()); } else { return 0; } }
BOOL COpened::_IsViewFileLogEnabled(OpenedEntry *ple) { if (!_IsChangeFile(ple)) { return FALSE; // not even a file!
}
//
// Some of the ops create files so there's nothing to see.
//
if (ple->IsAddLike()) { return FALSE; }
return TRUE; }
LRESULT COpened::_ViewFileLog(OpenedEntry *poe) { if (!_IsViewFileLogEnabled(poe)) { return 0; }
Substring ss; if (Parse(TEXT("$P"), poe->GetComment(), &ss)) { String str; str << TEXT("-#") << ss._pszMax << TEXT(" ") << ss; LaunchThreadTask(CFileLog_ThreadProc, str); } return 0; }
LRESULT COpened::ON_WM_SETCURSOR(UINT uiMsg, WPARAM wParam, LPARAM lParam) { return BGFilterSetCursor(super::HandleMessage(uiMsg, wParam, lParam)); }
LRESULT COpened::ON_WM_COMMAND(UINT uiMsg, WPARAM wParam, LPARAM lParam) { int iChange, iBug;
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDM_VIEWFILEDIFF: return _OnItemActivate(OEGetCurSel());
case IDM_VIEWBUG: iBug = _GetBugNumber(OEGetCurSel()); if (iBug) { OpenBugWindow(_hwnd, iBug); } break;
case IDM_VIEWFILELOG: _ViewFileLog(OEGetCurSel()); break; } return super::HandleMessage(uiMsg, wParam, lParam); }
void COpened::_AdjustMenu(HMENU hmenu, OpenedEntry *ple, BOOL fContextMenu) { AdjustBugMenu(hmenu, _GetBugNumber(ple), fContextMenu);
BOOL fEnable = _IsViewFileLogEnabled(ple); EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILELOG, fEnable, fContextMenu);
fEnable = _IsChangeFile(ple); EnableDisableOrRemoveMenuItem(hmenu, IDM_VIEWFILEDIFF, fEnable, fContextMenu);
MakeMenuPretty(hmenu); }
LRESULT COpened::ON_WM_INITMENU(UINT uiMsg, WPARAM wParam, LPARAM lParam) { _AdjustMenu(RECAST(HMENU, wParam), OEGetCurSel(), FALSE); return 0; }
LRESULT COpened::_OnGetContextMenu(OpenedEntry *ple) { HMENU hmenu = LoadPopupMenu(MAKEINTRESOURCE(IDM_OPENED_POPUP)); if (hmenu) { _AdjustMenu(hmenu, ple, TRUE); } return RECAST(LRESULT, hmenu); }
LRESULT COpened::_OnItemActivate(OpenedEntry *ple) { if (_IsChangeFile(ple)) { //
// Map the full depot path to a local file so we can pass it
// to windiff. We can't use "sd diff" because that will fail
// on a borrowed enlistment.
//
String strLocal; if (MapToLocalPath(ple->GetComment(), strLocal)) { Substring ss; if (Parse(TEXT("$p"), strLocal, &ss)) { String str; str << TEXT("windiff "); if (ple->IsAddLike()) { str << TEXT("nul "); } else { str << QuoteSpaces(ple->GetComment()); } str << TEXT(" "); if (ple->IsDelLike()) { str << TEXT("nul "); } else { str << QuoteSpaces(ss.Finalize()); } SpawnProcess(str); } } } return 0; }
LRESULT COpened::ON_WM_NOTIFY(UINT uiMsg, WPARAM wParam, LPARAM lParam) { NMTREELIST *ptl = RECAST(NMTREELIST*, lParam); OpenedEntry *ple;
switch (ptl->hdr.code) { case TLN_GETDISPINFO: ple = SAFECAST(OpenedEntry*, ptl->pti); if (ptl->iSubItem < 0) { return ple->GetDispInfo(ptl, ptl->iSubItem); } else if (ptl->iSubItem < 2) { return ple->GetDispInfo(ptl, ptl->iSubItem); } else { ASSERT(0); // invalid column
return 0; }
case TLN_ITEMACTIVATE: ple = SAFECAST(OpenedEntry*, ptl->pti); return _OnItemActivate(ple);
case TLN_GETINFOTIP: ple = SAFECAST(OpenedEntry*, ptl->pti); return ple->GetInfoTip(ptl);
case TLN_DELETEITEM: ple = SAFECAST(OpenedEntry*, ptl->pti); delete ple; return 0;
case TLN_GETCONTEXTMENU: ple = SAFECAST(OpenedEntry*, ptl->pti); return _OnGetContextMenu(ple); }
return super::HandleMessage(uiMsg, wParam, lParam); }
LRESULT COpened::ON_OM_INITIALIZED(UINT uiMsg, WPARAM wParam, LPARAM lParam) { _tree.Expand(_tree.GetRoot());
//
// Also expand the first changelist since it's usually what
// you are interested in.
//
TreeItem *pti = _tree.GetRoot()->FirstChild(); if (pti) { _tree.Expand(pti); }
return 0; }
LRESULT COpened::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_NOTIFY); FW_MSG(OM_INITIALIZED); }
return super::HandleMessage(uiMsg, wParam, lParam); }
//
// A private helper class that captures the parsing state machine.
//
class PendingChangesParseState { public: PendingChangesParseState() : _poeCurrent(NULL), _poeInsertAfter(NULL) { }
OpenedEntry *GetCurrent() const { return _poeCurrent; }
void Flush(Tree& tree) { if (_poeCurrent) { //
// Trim the trailing CRLF off the last line of the full
// description.
//
_strFullDescription.Chomp(); _poeCurrent->SetFullDescription(_strFullDescription); tree.RedrawItem(_poeCurrent); _poeCurrent = NULL; } _fHaveComment = FALSE; _strFullDescription.Reset(); }
void AddEntry(Tree &tree, String& str, Substring *rgss) { OpenedEntry *poe = new OpenedEntry(ChangeList(rgss[0].Finalize()), // Change
NULL); // Comment
if (poe) { if (tree.Insert(poe, tree.GetRoot(), _poeInsertAfter)) { _poeInsertAfter = _poeCurrent = poe; } else { delete poe; } } }
void SetEntry(OpenedEntry *poe) { _poeCurrent = poe; }
void AddLine(const String& str) { _strFullDescription << str; }
//
// We cannot use the CommentParser because we don't have a Dev
// column; besides, we don't want to handle proxy checkins here.
// Show the real unfiltered checkin comment.
//
void AddComment(LPTSTR psz) { if (_fHaveComment) return; if (!_poeCurrent) return;
//
// Ignore leading spaces.
//
while (*psz == TEXT('\t') || *psz == TEXT(' ')) psz++;
//
// Skip blank description lines.
//
if (*psz == TEXT('\0')) return;
//
// Use the first nonblank comment line as the text and toss the rest.
//
// Change all tabs to spaces because listview doesn't like tabs.
//
ChangeTabsToSpaces(psz);
_poeCurrent->SetComment(psz); _fHaveComment = TRUE; }
private: BOOL _fHaveComment; OpenedEntry*_poeCurrent; OpenedEntry*_poeInsertAfter; String _strFullDescription; };
DWORD CALLBACK COpened::s_BGInvoke(LPVOID lpParam) { COpened *self = RECAST(COpened *, lpParam); return self->_BGInvoke(); }
//
// Returns unparsed string (or NULL)
// and puts user name in pscUser.
//
//
LPCTSTR COpened::_BGParse(StringCache *pscUser) { /*
* Parse the switches as best we can. * */ GetOpt opt(TEXT("u"), _pszQuery); for (;;) {
switch (opt.NextSwitch()) { case TEXT('u'): *pscUser = opt.GetValue(); break;
case TEXT('\0'): goto L_switch; // two-level break
default: // Caller will display help for us
return NULL; } } L_switch:;
if (pscUser->IsEmpty()) { *pscUser = GlobalSettings.GetUserName(); }
String str; str << TEXT("sdv opened -u ") << *pscUser; SetWindowText(_hwnd, str);
/*
* The rest goes to "sd opened". */ return opt.GetTokenizer().Unparsed(); }
void COpened::_BGGetChanges(LPCTSTR pszUser) { LPCTSTR pszClient = GlobalSettings.GetClientName(); UINT cchClient = lstrlen(pszClient);
String str; str << TEXT("changes -l -s pending"); if (GlobalSettings.IsVersion(1, 60)) { str << TEXT(" -u ") << QuoteSpaces(pszUser); }
SDChildProcess proc(str); IOBuffer buf(proc.Handle()); PendingChangesParseState state; while (buf.NextLine(str)) { Substring rgss[4]; // changeno, date, domain\userid, client
if (Parse(TEXT("Change $d on $D by $u@$w"), str, rgss)) { state.Flush(_tree); if (rgss[3].Length() == cchClient && StrCmpNI(rgss[3].Start(), pszClient, cchClient) == 0) { state.AddLine(str); state.AddEntry(_tree, str, rgss); } } else if (state.GetCurrent()) { state.AddLine(str); if (str[0] == TEXT('\t')) { str.Chomp(); state.AddComment(str); } } } state.Flush(_tree); }
OpenedEntry *COpened::_BGFindChange(LPCTSTR pszChange, BOOL fCreate) { UINT uiKey = OpenedEntry::ComputeSortKey(pszChange); OpenedEntry *poeInsertAfter = NULL;
OpenedEntry *poe = SAFECAST(OpenedEntry *, _tree.GetRoot()->FirstChild()); if (poe == PTI_ONDEMAND) { poe = NULL; }
while (poe) { if (poe->GetSortKey() == uiKey) { return poe; }
if (poe->GetSortKey() < uiKey) { break; } poeInsertAfter = poe; poe = SAFECAST(OpenedEntry *, poe->NextSibling()); }
//
// Create it if necessary. (We always create "default".)
//
if (fCreate || StrCmp(pszChange, TEXT("default")) == 0) { poe = new OpenedEntry(ChangeList(pszChange), NULL); if (poe) { if (_tree.Insert(poe, _tree.GetRoot(), poeInsertAfter)) { return poe; } delete poe; } } return NULL; }
void COpened::_BGGetOpened(LPCTSTR pszArgs, LPCTSTR pszUser) { String str, strOrig; str << TEXT("opened ") << pszArgs;
SDChildProcess proc(str); IOBuffer buf(proc.Handle()); while (buf.NextLine(str)) { Substring rgss[6]; // path, version, op, changeno, type, user
LPTSTR pszRest = Parse(TEXT("$P#$d - $w "), str, rgss); if (pszRest) { strOrig = str;
rgss[1].Finalize(); // End of revision (path#version)
//
// Parsing is such sweet sorrow.
//
// "default change" but "change 1234".
//
LPTSTR pszRest2; if ((pszRest2 = Parse(TEXT("change $d $w"), pszRest, &rgss[3])) || (pszRest2 = Parse(TEXT("$w change $w"), pszRest, &rgss[3]))) { *pszRest2++ = TEXT('\0'); // relies on the fact that we didn't chomp
if (Parse(TEXT("by $p"), pszRest2, &rgss[5])) { // Some nondefault user, how about that
rgss[5].Finalize(); } else { // Default user
rgss[5].SetStart(GlobalSettings.GetUserName()); } if (lstrcmpi(rgss[5].Start(), pszUser) == 0) { OpenedEntry *poeParent = _BGFindChange(rgss[3].Finalize(), pszArgs[0]); if (poeParent) { OpenedEntry *poe = new OpenedEntry(PendingOp(rgss[2].Finalize()), rgss[0].Start()); if (poe) { if (_tree.Insert(poe, poeParent, PTI_APPEND)) { strOrig.Chomp(); poe->SetFullDescription(strOrig); } else { delete poe; } } } } } } } }
void COpened::_BGFillInChanges() { String str; str << TEXT("describe -s "); BOOL fAnyChanges = FALSE;
OpenedEntry *poe = SAFECAST(OpenedEntry *, _tree.GetRoot()->FirstChild()); if (poe == PTI_ONDEMAND) { poe = NULL; }
while (poe) { if (poe->GetSortKey() != OpenedEntry::SortKey_DefaultChange() && !poe->HasComment()) { str << poe->GetChange() << TEXT(" "); fAnyChanges = TRUE; } poe = SAFECAST(OpenedEntry *, poe->NextSibling()); }
if (fAnyChanges) { SDChildProcess proc(str); IOBuffer buf(proc.Handle()); PendingChangesParseState state; while (buf.NextLine(str)) { Substring rgss[4]; // changeno, domain\userid, client, date
if (Parse(TEXT("Change $d by $u@$w on $D"), str, rgss)) { state.Flush(_tree); state.AddLine(str); poe = _BGFindChange(rgss[0].Finalize(), FALSE); state.SetEntry(poe); } else if (state.GetCurrent()) { if (str[0] == TEXT('A')) { // "Affected files"
state.Flush(_tree); } else { state.AddLine(str); if (str[0] == TEXT('\t')) { str.Chomp(); state.AddComment(str); } } } } state.Flush(_tree); } }
DWORD COpened::_BGInvoke() { StringCache scUser; LPCTSTR pszUnparsed = _BGParse(&scUser); if (pszUnparsed) { // If no parameters, then go hunt down all the changelists
// so we can find the empty ones, too. Otherwise, we will
// figure them out as we see the results of "sd opened".
if (!*pszUnparsed) { _BGGetChanges(scUser); } _BGGetOpened(pszUnparsed, scUser); PostMessage(_hwnd, OM_INITIALIZED, 0, 0); _BGFillInChanges(); } else { Help(_hwnd, TEXT("#opene")); PostMessage(_hwnd, WM_CLOSE, 0, 0); } BGEndTask(); return 0; }
DWORD CALLBACK COpened_ThreadProc(LPVOID lpParameter) { return FrameWindow::RunThread(new COpened, lpParameter); }
|