/*++ Copyright (c) 2000 Microsoft Corporation Module Name: LogWindow.h Abstract: Implementation of the log window Author: Hakki T. Bostanci (hakkib) 06-Apr-2000 Revision History: --*/ #ifndef LOGWINDOW_H #define LOGWINDOW_H ////////////////////////////////////////////////////////////////////////// // // #include #include #include #include #include #include #include #pragma comment(lib, "user32") #pragma comment(lib, "advapi32") #pragma comment(lib, "comctl32") #include "Conv.h" #include "Wrappers.h" #include "MyHeap.h" ////////////////////////////////////////////////////////////////////////// // // #define ID_EDIT_COMMENT 1132 #define ID_COPY_MESSAGE 1133 #define ID_PAUSE_OUTPUT 1134 #define IDB_STATES 999 #ifndef LOG_LEVELS #define TLS_LOGALL 0x0000FFFFL // Log output. Logs all the time. #define TLS_LOG 0x00000000L // Log output. Logs all the time. #define TLS_INFO 0x00002000L // Log information. #define TLS_ABORT 0x00000001L // Log Abort, then kill process. #define TLS_SEV1 0x00000002L // Log at Severity 1 level #define TLS_SEV2 0x00000004L // Log at Severity 2 level #define TLS_SEV3 0x00000008L // Log at Severity 3 level #define TLS_WARN 0x00000010L // Log at Warn level #define TLS_PASS 0x00000020L // Log at Pass level #define TLS_BLOCK 0x00000400L // Block the variation. #define TLS_BREAK 0x00000800L // Debugger break; #define TLS_CALLTREE 0x00000040L // Log call-tree (function tracking). #define TLS_SYSTEM 0x00000080L // Log System debug. #define TLS_TESTDEBUG 0x00001000L // Debug level. #define TLS_TEST 0x00000100L // Log Test information (user). #define TLS_VARIATION 0x00000200L // Log testcase level. #endif //LOG_LEVELS ////////////////////////////////////////////////////////////////////////// // // class CLogWindow { private: CLogWindow(const CLogWindow &) {} CLogWindow &operator =(const CLogWindow &) { return *this; } public: CLogWindow(); ~CLogWindow(); void Create(PCTSTR pTitle, HWND hWndParent, HICON hIcon, ULONG nMaxNumMessages = -1); void Destroy(); PCTSTR Log( int nLogLevel, PCTSTR pszFileName, int nCode, PCTSTR pszMessage, PCTSTR pszStatus ) const; void SetTitle(PCTSTR pTitle) { m_pTitle = pTitle; SetWindowText(m_hWnd, pTitle); } DWORD WaitForSingleObject( DWORD dwMilliseconds = INFINITE, BOOL bAlertable = FALSE ) const { return m_Closed.WaitForSingleObject(dwMilliseconds, bAlertable); } BOOL IsClosed() const { return m_bIsClosed; } void Close(bool bClose = true) { if (bClose) { InterlockedExchange(&m_bIsClosed, TRUE); m_Closed.Set(); } else { InterlockedExchange(&m_bIsClosed, FALSE); m_Closed.Reset(); } } BOOL IsDirty() const { return m_bIsDirty; } HWND GetSafeHwnd() const { return m_hWnd; } HANDLE GetWaitHandle() const { return m_Closed; } class CSavedData; struct CListData { CMyStr m_strMsgNum; CMyStr m_strLogLevel; CMyStr m_strFileName; CMyStr m_strCode; CMyStr m_strMessage; CMyStr m_strComment; CListData( PCTSTR pszMsgNum, PCTSTR pszLogLevel, PCTSTR pszFileName, PCTSTR pszCode, PCTSTR pszMessage, const CSavedData &rSavedData ); explicit CListData(PTSTR pszLine); void * operator new(size_t, void *pPlacement) { return pPlacement; } #if _MSC_VER >= 1200 void operator delete(void *, void *) { } #endif void * operator new(size_t nSize) { return g_MyHeap.allocate(nSize); } void operator delete(void *pMem) { g_MyHeap.deallocate(pMem); } bool operator ==(const CListData &rhs) const; bool operator <(const CListData &rhs) const; void Write(FILE *fOut) const; static PCTSTR GetArg(PTSTR *pszStr); }; class CSavedData : public std::set, CMyAlloc > { public: void Read(FILE *fIn); void Write(FILE *fOut, PCTSTR pComments = 0) const; void Merge( const CSavedData &rNewData, CSavedData &rCollisions ); CMyStr m_strUserName; }; BOOL ReadSavedData(PCTSTR pFileName); BOOL WriteSavedData(PCTSTR pFileName, PCTSTR pComments = 0); BOOL WriteModifiedData(PCTSTR pFileName, PCTSTR pComments = 0); static BOOL FindNtLogLevel( DWORD dwLogLevel, PCTSTR *ppszText, int *piImage ); private: typedef enum { ID_FIRSTCOLUMN = 0, ID_LOGLEVEL = 0, ID_MSGNUM = 1, ID_CODE = 2, ID_FILENAME = 3, ID_MESSAGE = 4, ID_COMMENT = 5, ID_LASTCOLUMN = 5 }; static CLogWindow *This(HWND hWnd) { return (CLogWindow *) GetWindowLongPtr(hWnd, GWLP_USERDATA); } CListData *GetItemData(int nItem) const { return (CListData *) ListView_GetItemData(m_hList, nItem); } static DWORD WINAPI ThreadProc(PVOID pParameter); static ATOM RegisterLogWindow(HICON hIcon); static LRESULT CALLBACK DialogProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); LRESULT OnCreate(HWND hWnd); LRESULT OnClose(); LRESULT OnSize(UINT nType, int nW, int nH); LRESULT OnCopyToClipboard() const; BOOL CopySelectedItemsToBuffer( HANDLE hMem, PDWORD pdwBufferSize ) const; LRESULT OnEditComment() const; LRESULT OnPauseOutput(); void PlaceEditControlOnEditedItem(HWND hEdit) const; LRESULT OnGetDispInfo(NMLVDISPINFO *pnmv); LRESULT OnColumnClick(LPNMLISTVIEW pnmv); static int CALLBACK CompareFunc( LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort ); LRESULT OnBeginLabelEdit(NMLVDISPINFO *pdi); LRESULT OnEndLabelEdit(NMLVDISPINFO *pdi); LRESULT OnKeyDown(LPNMLVKEYDOWN pnkd); LRESULT OnRButtonDown(); public: CSavedData m_SavedData; private: PCTSTR m_pTitle; HWND m_hWndParent; HICON m_hIcon; Event m_InitComplete; CThread m_Thread; HWND m_hWnd; HWND m_hList; HWND m_hStatWnd; LONG m_bIsClosed; Event m_Closed; int m_SortByHistory[ID_LASTCOLUMN - ID_FIRSTCOLUMN + 1]; mutable LONG m_nMsgNum; int m_nEditedItem; int m_bIsDirty; LONG m_bIsPaused; Event m_Resume; ULONG m_nMaxNumMessages; mutable CMySimpleCriticalSection m_cs; }; #ifdef IMPLEMENT_LOGWINDOW ////////////////////////////////////////////////////////////////////////// // // // CLogWindow::CLogWindow() : m_bIsClosed(TRUE), m_Closed(TRUE, TRUE), m_bIsPaused(FALSE), m_Resume(TRUE, TRUE) { m_hWndParent = 0; m_hWnd = 0; m_nMsgNum = 0; for (int i = 0; i < COUNTOF(m_SortByHistory); ++i) { m_SortByHistory[i] = 0; } m_bIsDirty = FALSE; } ////////////////////////////////////////////////////////////////////////// // // // CLogWindow::~CLogWindow() { Destroy(); } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::Create(PCTSTR pTitle, HWND hWndParent, HICON hIcon, ULONG nMaxNumMessages /*= -1*/) { m_pTitle = pTitle; m_hWndParent = hWndParent; m_hIcon = hIcon; m_nMaxNumMessages = nMaxNumMessages; m_InitComplete = Event(TRUE, FALSE); m_Thread = CThread(ThreadProc, this); m_InitComplete.WaitForSingleObject(); m_InitComplete.Detach(); } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::Destroy() { //bugbug: this should be handled in WM_DESTROY int nCount = ListView_GetItemCount(m_hList); for (int i = 0; i < nCount; ++i) { CListData *pData = GetItemData(i); ListView_SetItemData(m_hList, i, 0); delete pData; } if (m_Thread.IsAttached()) { PostThreadMessage(m_Thread, WM_QUIT, 0, 0); m_Thread.WaitForSingleObject(); m_Thread.Detach(); } m_hWnd = 0; m_nMsgNum = 0; } ////////////////////////////////////////////////////////////////////////// // // // PCTSTR CLogWindow::Log( int nLogLevel, PCTSTR pszFileName, int nCode, PCTSTR pszMessage, PCTSTR pszStatus ) const { if (m_bIsPaused) { m_Resume.WaitForSingleObject(); } // get a new entry number for this item LONG nMsgNum = InterlockedIncrement(&m_nMsgNum); if ((ULONG) nMsgNum >= m_nMaxNumMessages) { m_cs.Enter(); CListData *pData = GetItemData(0); ListView_DeleteItem(m_hList, 0); m_cs.Leave(); delete pData; } // prepare the CListData entry for this item PCTSTR pszLogLevelText; int nLogLevelImage; FindNtLogLevel(nLogLevel, &pszLogLevelText, &nLogLevelImage); TCHAR szMsgNum[48]; _itot(nMsgNum, szMsgNum, 10); TCHAR szCode[48]; _itot(nCode, szCode, 10); CListData *pData = new CListData( szMsgNum, pszLogLevelText, pszFileName, szCode, pszMessage, m_SavedData ); // insert this new item LVITEM item; item.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_TEXT; item.iItem = nMsgNum - 1; item.iSubItem = 0; item.iImage = nLogLevelImage; item.lParam = (LPARAM) pData; item.pszText = LPSTR_TEXTCALLBACK; int iItem = ListView_InsertItem(m_hList, &item); ListView_EnsureVisible(m_hList, iItem, TRUE); SendMessage(m_hStatWnd, SB_SETTEXT, (WPARAM) SB_SIMPLEID, (LPARAM) pszStatus); return pData->m_strComment; } ////////////////////////////////////////////////////////////////////////// // // // BOOL CLogWindow::FindNtLogLevel( DWORD dwLogLevel, PCTSTR *ppszText, int *piImage ) { dwLogLevel &= ~( TLS_CALLTREE | TLS_BREAK | TLS_SYSTEM | TLS_TESTDEBUG | TLS_TEST | TLS_VARIATION ); switch (dwLogLevel) { case TLS_INFO: *ppszText = _T("Info"); *piImage = 5; return TRUE; case TLS_ABORT: *ppszText = _T("Abort"); *piImage = 0; return TRUE; case TLS_SEV1: *ppszText = _T("Sev1"); *piImage = 1; return TRUE; case TLS_SEV2: *ppszText = _T("Sev2"); *piImage = 2; return TRUE; case TLS_SEV3: *ppszText = _T("Sev3"); *piImage = 3; return TRUE; case TLS_WARN: *ppszText = _T("Warn"); *piImage = 4; return TRUE; case TLS_PASS: *ppszText = _T("Pass"); *piImage = 0; return TRUE; case TLS_BLOCK: *ppszText = _T("Block"); *piImage = 0; return TRUE; } *ppszText = 0; *piImage = 0; return FALSE; } ////////////////////////////////////////////////////////////////////////// // // // DWORD WINAPI CLogWindow::ThreadProc(PVOID pParameter) { CLogWindow *that = (CLogWindow *) pParameter; static ATOM pClassName = RegisterLogWindow(that->m_hIcon); CreateWindow( (PCTSTR) pClassName, that->m_pTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 785, 560, that->m_hWndParent, 0, GetModuleHandle(0), that ); MSG msg; while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } ////////////////////////////////////////////////////////////////////////// // // // ATOM CLogWindow::RegisterLogWindow(HICON hIcon) { WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpfnWndProc = DialogProc; wcex.hInstance = GetModuleHandle(0); wcex.hIcon = hIcon; wcex.hCursor = LoadCursor(0, IDC_ARROW); wcex.lpszClassName = _T("LOGWINDOW"); return RegisterClassEx(&wcex); } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CALLBACK CLogWindow::DialogProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_CREATE: return ((CLogWindow *)((LPCREATESTRUCT)lParam)->lpCreateParams)->OnCreate(hWnd); case WM_CLOSE: return This(hWnd)->OnClose(); case WM_SIZE: return This(hWnd)->OnSize((UINT)wParam, LOWORD(lParam), HIWORD(lParam)); case WM_COMMAND: { CLogWindow *that = This(hWnd); HWND hEdit = ListView_GetEditControl(that->m_hList); if (hEdit != 0 && hEdit == (HWND) lParam) { that->PlaceEditControlOnEditedItem(hEdit); } else { switch (LOWORD(wParam)) { case ID_COPY_MESSAGE: return that->OnCopyToClipboard(); case ID_EDIT_COMMENT: return that->OnEditComment(); case ID_PAUSE_OUTPUT: return that->OnPauseOutput(); } } break; } case WM_USER + 1: { CLogWindow *that = This(hWnd); HWND hEdit = ListView_GetEditControl(that->m_hList); if (hEdit != 0) { that->PlaceEditControlOnEditedItem(hEdit); } break; } case WM_NOTIFY: switch (((LPNMHDR) lParam)->code) { case LVN_GETDISPINFO: return This(hWnd)->OnGetDispInfo((NMLVDISPINFO *) lParam); case LVN_COLUMNCLICK: return This(hWnd)->OnColumnClick((LPNMLISTVIEW) lParam); case LVN_BEGINLABELEDIT: return This(hWnd)->OnBeginLabelEdit((NMLVDISPINFO *) lParam); case LVN_ENDLABELEDIT: return This(hWnd)->OnEndLabelEdit((NMLVDISPINFO *) lParam); case LVN_KEYDOWN: return This(hWnd)->OnKeyDown((LPNMLVKEYDOWN) lParam); case NM_RCLICK: return This(hWnd)->OnRButtonDown(); } break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnCreate(HWND hWnd) { m_hWnd = hWnd; SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR) this); m_hStatWnd = CreateStatusWindow(WS_CHILD | WS_VISIBLE, 0, m_hWnd, 0); SendMessage(m_hStatWnd, SB_SIMPLE, (WPARAM) TRUE, (LPARAM) 0); m_hList = CreateWindow( _T("SysListView32"), 0, WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_EDITLABELS, 0, 0, 0, 0, m_hWnd, 0, GetModuleHandle(0), 0 ); ListView_SetExtendedListViewStyle( m_hList, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP ); HIMAGELIST hImgList = ImageList_LoadBitmap( GetModuleHandle(0), MAKEINTRESOURCE(IDB_STATES), 16, 0, RGB(255, 255, 255) ); if (hImgList) { ListView_SetImageList(m_hList, hImgList, LVSIL_SMALL); } ListView_InsertColumn2(m_hList, 0, _T("Type"), LVCFMT_LEFT, 60, ID_LOGLEVEL); ListView_InsertColumn2(m_hList, 1, _T("#"), LVCFMT_LEFT, 40, ID_MSGNUM); ListView_InsertColumn2(m_hList, 2, _T("Code"), LVCFMT_LEFT, 40, ID_CODE); ListView_InsertColumn2(m_hList, 3, _T("File"), LVCFMT_LEFT, 100, ID_FILENAME); ListView_InsertColumn2(m_hList, 4, _T("Message"), LVCFMT_LEFT, 420, ID_MESSAGE); ListView_InsertColumn2(m_hList, 5, _T("Comment"), LVCFMT_LEFT, 100, ID_COMMENT); SetForegroundWindow(m_hList); InterlockedExchange(&m_bIsClosed, FALSE); m_Closed.Reset(); m_InitComplete.Set(); return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnClose() { if (!m_bIsClosed) { InterlockedExchange(&m_bIsClosed, TRUE); m_Closed.Set(); if (m_bIsPaused) { InterlockedExchange(&m_bIsPaused, FALSE); m_Resume.Set(); } TCHAR szNewTitle[MAX_PATH]; _tcscpy(szNewTitle, m_pTitle); _tcscat(szNewTitle, _T(" "));; SetWindowText(m_hWnd, szNewTitle); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnSize(UINT nType, int nW, int nH) { RECT r; GetWindowRect(m_hStatWnd, &r); int nStatWndH = r.bottom - r.top; MoveWindow(m_hList, 0, 0, nW, nH - nStatWndH, TRUE); TCHAR szBuffer[1024]; // ***bugbug: figure out why the statwnd forgets its caption SendMessage(m_hStatWnd, SB_GETTEXT, (WPARAM) SB_SIMPLEID, (LPARAM) szBuffer); MoveWindow(m_hStatWnd, 0, nH - nStatWndH, nW, nStatWndH, TRUE); SendMessage(m_hStatWnd, SB_SETTEXT, (WPARAM) SB_SIMPLEID, (LPARAM) szBuffer); return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnCopyToClipboard() const { HANDLE hMem = 0; for ( DWORD dwSize = 4 * 1024; (hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, dwSize)) != 0 && !CopySelectedItemsToBuffer(hMem, &dwSize); GlobalFree(hMem) ) { // start with a 4KB buffer size // if buffer allocation fails, exit // if CopySelectedItemsToBuffer succeeds, exit // otherwise, delete the buffer and retry } if (hMem) { BOOL bResult = FALSE; if (OpenClipboard(m_hWnd)) { if (EmptyClipboard()) { if (SetClipboardData(CF_TEXT, hMem)) { bResult = TRUE; } } CloseClipboard(); } if (!bResult) { GlobalFree(hMem); } } return 0; } ////////////////////////////////////////////////////////////////////////// // // // BOOL CLogWindow::CopySelectedItemsToBuffer( HANDLE hMem, PDWORD pdwBufferSize ) const { PSTR pMem = (PSTR) GlobalLock(hMem); CBufferFill Buffer(pMem, *pdwBufferSize); if (pMem) { int nItem = -1; while ((nItem = ListView_GetNextItem(m_hList, nItem, LVNI_SELECTED)) != -1) { USES_CONVERSION; CListData *pData = GetItemData(nItem); if (pData->m_strFileName && *pData->m_strFileName) { Buffer.AddTop(CStrBlob(T2A(pData->m_strFileName))); Buffer.AddTop(CStrBlob(": ")); } Buffer.AddTop(CStrBlob(T2A(pData->m_strMessage))); if (pData->m_strComment && *pData->m_strComment) { Buffer.AddTop(CStrBlob(" (")); Buffer.AddTop(CStrBlob(T2A(pData->m_strComment))); Buffer.AddTop(CStrBlob(")")); } Buffer.AddTop(CStrBlob("\r\n")); } Buffer.AddTop(CSzBlob("")); // terminating NULL GlobalUnlock(hMem); } *pdwBufferSize -= (DWORD) Buffer.BytesLeft(); return Buffer.BytesLeft() >= 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnEditComment() const { if (m_nEditedItem != -1) { ListView_EditLabel(m_hList, m_nEditedItem); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnPauseOutput() { if (m_bIsPaused) { InterlockedExchange(&m_bIsPaused, FALSE); m_Resume.Set(); SetWindowText(m_hWnd, m_pTitle); } else { InterlockedExchange(&m_bIsPaused, TRUE); m_Resume.Reset(); TCHAR szNewTitle[MAX_PATH]; _tcscpy(szNewTitle, m_pTitle); _tcscat(szNewTitle, _T(" "));; SetWindowText(m_hWnd, szNewTitle); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::PlaceEditControlOnEditedItem(HWND hEdit) const { RECT r; ListView_GetSubItemRect( m_hList, m_nEditedItem, ID_COMMENT, LVIR_LABEL, &r ); MoveWindow( hEdit, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE ); } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnGetDispInfo(NMLVDISPINFO *pnmv) { ASSERT(pnmv->item.mask == LVIF_TEXT); CListData *pData = (CListData *) pnmv->item.lParam; if (pData) { switch (pnmv->item.iSubItem) { case ID_LOGLEVEL: pnmv->item.pszText = pData->m_strLogLevel; break; case ID_MSGNUM: pnmv->item.pszText = pData->m_strMsgNum; break; case ID_CODE: pnmv->item.pszText = pData->m_strCode; break; case ID_FILENAME: pnmv->item.pszText = pData->m_strFileName; break; case ID_MESSAGE: pnmv->item.pszText = pData->m_strMessage; break; case ID_COMMENT: pnmv->item.pszText = pData->m_strComment; break; default: ASSERT(FALSE); } ASSERT(pnmv->item.pszText); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnColumnClick(LPNMLISTVIEW pnmv) { // convert zero based column number to one based, as // we will use negative numbers to indicate sort direction int nColumn = pnmv->iSubItem + 1; if (Abs(m_SortByHistory[0]) == nColumn) { // if the user has pressed a column header twice, // reverse the sort direction m_SortByHistory[0] *= -1; } else { // if the user has selected a new column, slide down // the history list and enter the new column to the top // position. Also keep the last entry, that will eventually // end the recursive CompareFunc in the worst case for (int i = COUNTOF(m_SortByHistory) - 2; i >= 1; --i) { m_SortByHistory[i] = m_SortByHistory[i-1]; } m_SortByHistory[0] = nColumn; } // sort the items ListView_SortItems(m_hList, CompareFunc, m_SortByHistory); // ensure that the selected item is visible ListView_EnsureVisible( m_hList, ListView_GetNextItem(m_hList, -1, LVNI_SELECTED), TRUE ); return 0; } ////////////////////////////////////////////////////////////////////////// // // // int CALLBACK CLogWindow::CompareFunc( LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort ) { CListData *pData1 = (CListData *) lParam1; CListData *pData2 = (CListData *) lParam2; int *SortByHistory = (int *) lParamSort; ASSERT(pData1); ASSERT(pData2); ASSERT(SortByHistory); int nSortDir = 1; int nColumn = SortByHistory[0]; if (nColumn < 0) { nSortDir *= -1; nColumn *= -1; } int nResult = 0; switch (nColumn) { case 0: nResult = -1; break; case ID_MSGNUM + 1: nResult = Cmp(_ttoi(pData1->m_strMsgNum), _ttoi(pData2->m_strMsgNum)); break; case ID_LOGLEVEL + 1: nResult = _tcscmp(pData1->m_strLogLevel, pData2->m_strLogLevel); break; case ID_FILENAME + 1: nResult = _tcscmp(pData1->m_strFileName, pData2->m_strFileName); break; case ID_CODE + 1: nResult = Cmp(_ttoi(pData1->m_strCode), _ttoi(pData2->m_strCode)); break; case ID_MESSAGE + 1: nResult = _tcscmp(pData1->m_strMessage, pData2->m_strMessage); break; case ID_COMMENT + 1: nResult = _tcscmp(pData1->m_strComment, pData2->m_strComment); break; default: ASSERT(FALSE); } // in case of equality, go on with the comparison using the next // column in the history list return nResult != 0 ? nSortDir * nResult : CompareFunc(lParam1, lParam2, (LPARAM) (SortByHistory + 1)); } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnBeginLabelEdit(NMLVDISPINFO *pdi) { HWND hEdit = ListView_GetEditControl(m_hList); if (hEdit) { CListData *pData = GetItemData(pdi->item.iItem); SetWindowText(hEdit, pData->m_strComment); m_nEditedItem = pdi->item.iItem; PostMessage(m_hWnd, WM_USER + 1, 0, 0); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnEndLabelEdit(NMLVDISPINFO *pdi) { if (pdi->item.iItem != -1 && pdi->item.pszText != 0) { CListData *pData = GetItemData(pdi->item.iItem); if (_tcscmp(pData->m_strComment, pdi->item.pszText) != 0) { // save the comment pData->m_strComment = pdi->item.pszText; // replace the tabs (if any) with space, we use tabs as // the column delimiter character for ( PTSTR pszTab = _tcschr(pData->m_strComment, _T('\t')); pszTab; pszTab = _tcschr(pszTab + 1, _T('\t')) ) { *pszTab = _T(' '); } ListView_SetItemText(m_hList, pdi->item.iItem, ID_COMMENT, pData->m_strComment); // save this new entry m_SavedData.erase(*pData); m_SavedData.insert(*pData); m_bIsDirty = TRUE; } } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnKeyDown(LPNMLVKEYDOWN pnkd) { if (pnkd->wVKey == VK_INSERT && GetKeyState(VK_CONTROL) < 0) // CTRL+INS { return OnCopyToClipboard(); } else if (pnkd->wVKey == VK_RETURN) { m_nEditedItem = ListView_GetSelectionMark(m_hList); return OnEditComment(); } else if (pnkd->wVKey == VK_PAUSE) { return OnPauseOutput(); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // LRESULT CLogWindow::OnRButtonDown() { POINT p; GetCursorPos(&p); LVHITTESTINFO lvHitTestInfo; lvHitTestInfo.pt = p; ScreenToClient(m_hList, &lvHitTestInfo.pt); ListView_HitTest(m_hList, &lvHitTestInfo); m_nEditedItem = lvHitTestInfo.iItem; HMENU hMenu = CreatePopupMenu(); if (hMenu) { AppendMenu(hMenu, MF_STRING, ID_EDIT_COMMENT, _T("&Edit\tEnter")); AppendMenu(hMenu, MF_STRING, ID_COPY_MESSAGE, _T("&Copy\tCtrl+Ins")); AppendMenu(hMenu, MF_STRING, ID_PAUSE_OUTPUT, m_bIsPaused ? _T("&Resume") : _T("&Pause")); TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN, p.x, p.y, 0, m_hWnd, 0 ); DestroyMenu(hMenu); } return 0; } ////////////////////////////////////////////////////////////////////////// // // // CLogWindow::CListData::CListData( PCTSTR pszMsgNum, PCTSTR pszLogLevel, PCTSTR pszFileName, PCTSTR pszCode, PCTSTR pszMessage, const CSavedData &rSavedData ) : m_strMsgNum(pszMsgNum), m_strLogLevel(pszLogLevel), m_strFileName(pszFileName), m_strCode(pszCode), m_strMessage(pszMessage) { CSavedData::const_iterator itData = rSavedData.find(*this); m_strComment = itData != rSavedData.end() ? itData->m_strComment : _T(""); } ////////////////////////////////////////////////////////////////////////// // // // CLogWindow::CListData::CListData( PTSTR pszLine ) : m_strLogLevel(GetArg(&pszLine)), m_strFileName(GetArg(&pszLine)), m_strCode(GetArg(&pszLine)), m_strMessage(GetArg(&pszLine)), m_strComment(GetArg(&pszLine)) { } ////////////////////////////////////////////////////////////////////////// // // // bool CLogWindow::CListData::operator ==(const CListData &rhs) const { return _tcscmp(m_strMessage, rhs.m_strMessage) == 0 && _tcscmp(m_strCode, rhs.m_strCode) == 0 && _tcscmp(m_strFileName, rhs.m_strFileName) == 0 && _tcscmp(m_strLogLevel, rhs.m_strLogLevel) == 0; } ////////////////////////////////////////////////////////////////////////// // // // bool CLogWindow::CListData::operator <(const CListData &rhs) const { int nCmpMessage, nCmpCode, nCmpFileName, nCmpLogLevel; return ((nCmpMessage = _tcscmp(m_strMessage, rhs.m_strMessage)) < 0 || (nCmpMessage == 0 && ((nCmpCode = _tcscmp(m_strCode, rhs.m_strCode)) < 0 || (nCmpCode == 0 && ((nCmpFileName = _tcscmp(m_strFileName, rhs.m_strFileName)) < 0 || (nCmpFileName == 0 && ((nCmpLogLevel = _tcscmp(m_strLogLevel, rhs.m_strLogLevel)) < 0) ) ) ) ) ) ); } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::CListData::Write(FILE *fOut) const { _ftprintf( fOut, _T("%s\t%s\t%s\t%s\t%s\r\n"), m_strLogLevel, m_strFileName, m_strCode, m_strMessage, m_strComment ); } ////////////////////////////////////////////////////////////////////////// // // // PCTSTR CLogWindow::CListData::GetArg(PTSTR *pszStr) { PTSTR pszStart = *pszStr; PTSTR pszEnd = pszStart; while ( *pszEnd != '\t' && *pszEnd != '\n' && *pszEnd != '\r' && *pszEnd != '\0' ) { pszEnd = CharNext(pszEnd); } *pszStr = CharNext(pszEnd); while ( **pszStr == '\n' || **pszStr == '\r' ) { *pszStr = CharNext(*pszStr); } *pszEnd = '\0'; return pszStart; } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::CSavedData::Read(FILE *fIn) { TCHAR szLine[4096]; while (_fgetts(szLine, COUNTOF(szLine), fIn)) { if (szLine[0] == _T('#')) { *FindEol(szLine) = '\0'; m_strUserName = szLine + 2; } else { insert(CListData(szLine)); } } } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::CSavedData::Write(FILE *fOut, PCTSTR pComments /*= 0*/) const { CUserName UserName; CComputerName ComputerName; _ftprintf( fOut, pComments && *pComments ? _T("# %s@%s %s\n") : _T("# %s@%s\n"), (PCTSTR) UserName, (PCTSTR) ComputerName, pComments ); for ( const_iterator itData = begin(); itData != end(); ++itData ) { itData->Write(fOut); } } ////////////////////////////////////////////////////////////////////////// // // // void CLogWindow::CSavedData::Merge( const CSavedData &rNewData, CSavedData &rCollisions ) { rCollisions.clear(); for ( const_iterator itData = rNewData.begin(); itData != rNewData.end(); ++itData ) { const_iterator itMatch = find(*itData); if ( itMatch != end() && _tcscmp(itMatch->m_strComment, itData->m_strComment) != 0 ) { rCollisions.insert(*itMatch); erase(itMatch); } insert(*itData); } } ////////////////////////////////////////////////////////////////////////// // // // BOOL CLogWindow::ReadSavedData(PCTSTR pFileName) { BOOL bResult = FALSE; try { m_SavedData.Read(CCFile(pFileName, _T("rt"))); bResult = TRUE; } catch (const CError &) { MessageBox( 0, _T("Cannot download the comments from the central database. ") _T("Previous comments will not be available on the results window."), 0, MB_ICONERROR | MB_OK ); } return bResult; } ////////////////////////////////////////////////////////////////////////// // // // BOOL CLogWindow::WriteSavedData(PCTSTR pFileName, PCTSTR pComments /*= 0*/) { BOOL bResult = FALSE; HANDLE hFile = CreateFile( pFileName, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); if (hFile != INVALID_HANDLE_VALUE) { FILE *fFile = OpenOSHandle(hFile, _O_TEXT, _T("wt")); m_SavedData.Write(fFile, pComments); bResult = fclose(fFile) == 0; } return bResult; /*BOOL bResult = FALSE; try { m_SavedData.Write(CCFile(pFileName, _T("wt")), PCTSTR pComments); bResult = TRUE; } catch (const CError &) { // } return bResult;*/ } ////////////////////////////////////////////////////////////////////////// // // // BOOL CLogWindow::WriteModifiedData(PCTSTR pFileName, PCTSTR pComments /*= 0*/) { BOOL bResult = TRUE; if ( IsDirty() && MessageBox( 0, _T("You have changed some of the comments on the results window. ") _T("Do you want to save these changes to the central database?"), m_pTitle, MB_ICONQUESTION | MB_YESNO ) == IDYES ) { bResult = FALSE; do { try { m_SavedData.Write(CCFile(pFileName, _T("wt")), pComments); MessageBox( 0, _T("Comments database updated successfully."), m_pTitle, MB_ICONINFORMATION | MB_OK ); bResult = TRUE; } catch (const CError &) { // bResult remains FALSE; } } while ( !bResult && MessageBox( 0, _T("Cannot update the results. Do you want to try again?"), 0, MB_ICONQUESTION | MB_YESNO ) == IDYES ); } return bResult; } #endif //IMPLEMENT_LOGWINDOW #endif //PROGRESSDLG_H