Click here to Skip to main content
15,897,519 members
Articles / Desktop Programming / MFC

A Realtime Logfile Viewer

Rate me:
Please Sign up or sign in to vote.
4.98/5 (39 votes)
24 Feb 2004CPOL20 min read 234.6K   4.9K   151  
Part two of the logging service - the viewer
/*
 *	$Header :$
 *
 *	$History: logviewerView.cpp $
 * 
 * *****************  Version 3  *****************
 * User: Administrator Date: 12/02/03   Time: 9:20p
 * Updated in $/logger/logviewer
 * Added pretty icons, version resource, tooltip support, fixed various
 * bugs in continuation record support.
 * 
 * *****************  Version 2  *****************
 * User: Administrator Date: 11/27/03   Time: 3:17p
 * Updated in $/logger/logviewer
 * Revamped the LogUpdateThread to post messages to the main thread for
 * dealing with the listcontrol.
 * 
 * *****************  Version 1  *****************
 * User: Administrator Date: 11/25/03   Time: 5:20p
 * Created in $/logger/logviewer
 */
#include "stdafx.h"
#include <atlconv.h>
#include "logviewer.h"
#include "LogData.h"
#include "MainFrm.h"

#include "logviewerDoc.h"
#include "logviewerView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CLogViewerView
IMPLEMENT_DYNCREATE(CLogViewerView, CListView)

BEGIN_MESSAGE_MAP(CLogViewerView, CListView)
	//{{AFX_MSG_MAP(CLogViewerView)
	ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)
	ON_COMMAND(IDC_ERRORS, OnErrors)
	ON_COMMAND(IDC_INFORMATION, OnInformation)
	ON_COMMAND(IDC_SUCCESS, OnSuccess)
	ON_COMMAND(IDC_WARNINGS, OnWarnings)
	ON_UPDATE_COMMAND_UI(IDC_ERRORS, OnUpdateErrors)
	ON_UPDATE_COMMAND_UI(IDC_INFORMATION, OnUpdateInformation)
	ON_UPDATE_COMMAND_UI(IDC_SUCCESS, OnUpdateSuccess)
	ON_UPDATE_COMMAND_UI(IDC_WARNINGS, OnUpdateWarnings)
	ON_COMMAND(IDC_CLEARSELECTION, OnClearSelection)
	ON_UPDATE_COMMAND_UI(IDC_CLEARSELECTION, OnUpdateClearSelection)
	ON_COMMAND(IDC_CLEAR, OnClear)
	ON_UPDATE_COMMAND_UI(IDC_CLEAR, OnUpdateClear)
	ON_WM_MOUSEMOVE()
	//}}AFX_MSG_MAP
	ON_REGISTERED_MESSAGE(guiAdvise, OnAdvise)
	ON_CBN_SELCHANGE(IDC_MODULECOMBO, OnSelChange)
	ON_CBN_SELCHANGE(IDC_THREADCOMBO, OnSelChange)
	ON_CBN_SELCHANGE(IDC_MACHINECOMBO, OnSelChange)
	ON_NOTIFY_RANGE(TTN_NEEDTEXTW, 0, 0xffff, OnToolTipText)
	ON_NOTIFY_RANGE(TTN_NEEDTEXTA, 0, 0xffff, OnToolTipText)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CLogViewerView construction/destruction
CLogViewerView::CLogViewerView() : CListView()
{
	m_bIsActiveWindow = FALSE;
	m_lastTip = (CLogData *) NULL;

	memset(m_tipText, 0, sizeof(m_tipText));
	m_logFileInstance = (CLogFileInstance *) NULL;
}

CLogViewerView::~CLogViewerView()
{
	delete m_logFileInstance;
}

BOOL CLogViewerView::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style |= LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP;

	m_tip.Create(this, TTS_ALWAYSTIP | TTS_BALLOON | TTS_NOPREFIX);
	m_tip.SetMaxTipWidth(400);
	return CListView::PreCreateWindow(cs);
}

BOOL CLogViewerView::OpenDocument(LPCTSTR szFileName)
{
	ASSERT(szFileName);
	ASSERT(AfxIsValidString(szFileName));

	delete m_logFileInstance;
	m_logFileInstance = new CLogFileInstance(this);

	ASSERT(m_logFileInstance);
	ASSERT_KINDOF(CLogFileInstance, m_logFileInstance);

	return m_logFileInstance->Open(szFileName);
}

/////////////////////////////////////////////////////////////////////////////
// CLogViewerView drawing
void CLogViewerView::OnDraw(CDC* /*pDC*/)
{
	CLogViewerDoc* pDoc = GetDocument();

	ASSERT_VALID(pDoc);
}

void CLogViewerView::OnInitialUpdate()
{
	//	We don't call the base class OnInitialUpdate because all it
	//	does is call our OnUpdate function. No point, we do nothing there....
//	CListView::OnInitialUpdate();

	CRect	   rect;
	CListCtrl& ctl = GetListCtrl();

	ctl.GetClientRect(&rect);

	ctl.InsertColumn(LOGVIEWER::COLUMN_IMAGE, _T(""), LVCFMT_LEFT, 20, LOGVIEWER::COLUMN_IMAGE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_DATE, _T("Timestamp"), LVCFMT_LEFT, 100, LOGVIEWER::COLUMN_DATE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_SEQUENCE, _T("Sequence"), LVCFMT_RIGHT, 100, LOGVIEWER::COLUMN_SEQUENCE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_CODE, _T("Code"), LVCFMT_RIGHT, 100, LOGVIEWER::COLUMN_CODE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_MODULE, _T("Module"), LVCFMT_LEFT, 100, LOGVIEWER::COLUMN_MODULE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_THREAD, _T("Thread"), LVCFMT_RIGHT, 100, LOGVIEWER::COLUMN_THREAD);
	ctl.InsertColumn(LOGVIEWER::COLUMN_MACHINE, _T("Machine"), LVCFMT_LEFT, 100, LOGVIEWER::COLUMN_MACHINE);
	ctl.InsertColumn(LOGVIEWER::COLUMN_MESSAGE, _T("Message"), LVCFMT_LEFT, rect.Width() - 600, LOGVIEWER::COLUMN_MESSAGE);
	ctl.SetExtendedStyle(ctl.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_INFOTIP);
	ctl.SetImageList(MAINFRAME->GetImageList(), LVSIL_SMALL);
}

/////////////////////////////////////////////////////////////////////////////
// CLogViewerView diagnostics
#ifdef _DEBUG
void CLogViewerView::AssertValid() const
{
	CListView::AssertValid();
}

void CLogViewerView::Dump(CDumpContext& dc) const
{
	CListView::Dump(dc);
}

CLogViewerDoc* CLogViewerView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CLogViewerDoc)));
	return (CLogViewerDoc *) m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CLogViewerView message handlers
void CLogViewerView::OnUpdate(CView* /*pSender*/, LPARAM /*lHint*/, CObject* /*pHint*/) 
{
	TRACE("CLogViewerView::OnUpdate\n");
}

void CLogViewerView::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) 
{
	LV_DISPINFO	  *pDispInfo = (LV_DISPINFO *) pNMHDR;
	LV_ITEM		  *pItem = &(pDispInfo)->item;
	
	if (pItem->mask & LVIF_TEXT)
	{
		CLogText *pLog = (CLogText *) pItem->lParam;

		ASSERT(pLog);
		ASSERT_KINDOF(CLogText, pLog);

		pLog->RenderText(*pItem, m_logFileInstance->m_logFilterData);
	}

	*pResult = 0;
}

void CLogViewerView::OnErrors() 
{
	if (m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::ERRORS)
		m_logFileInstance->m_logFilterData.m_iLevel &= ~LOGVIEWER::ERRORS;
	else
		m_logFileInstance->m_logFilterData.m_iLevel |= LOGVIEWER::ERRORS;

	GetListCtrl().DeleteAllItems();
	m_logFileInstance->Reload();
}

void CLogViewerView::OnInformation() 
{
	if (m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::INFORMATION)
		m_logFileInstance->m_logFilterData.m_iLevel &= ~LOGVIEWER::INFORMATION;
	else
		m_logFileInstance->m_logFilterData.m_iLevel |= LOGVIEWER::INFORMATION;

	GetListCtrl().DeleteAllItems();
	m_logFileInstance->Reload();
}

void CLogViewerView::OnSuccess() 
{
	if (m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::SUCCESS)
		m_logFileInstance->m_logFilterData.m_iLevel &= ~LOGVIEWER::SUCCESS;
	else
		m_logFileInstance->m_logFilterData.m_iLevel |= LOGVIEWER::SUCCESS;

	GetListCtrl().DeleteAllItems();
	m_logFileInstance->Reload();
}

void CLogViewerView::OnWarnings() 
{
	if (m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::WARNINGS)
		m_logFileInstance->m_logFilterData.m_iLevel &= ~LOGVIEWER::WARNINGS;
	else
		m_logFileInstance->m_logFilterData.m_iLevel |= LOGVIEWER::WARNINGS;

	GetListCtrl().DeleteAllItems();
	m_logFileInstance->Reload();
}

void CLogViewerView::OnUpdateErrors(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::ERRORS ? TRUE : FALSE);
}

void CLogViewerView::OnUpdateInformation(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::INFORMATION ? TRUE : FALSE);
}

void CLogViewerView::OnUpdateSuccess(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::SUCCESS ? TRUE : FALSE);
}

void CLogViewerView::OnUpdateWarnings(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck(m_logFileInstance->m_logFilterData.m_iLevel & LOGVIEWER::WARNINGS ? TRUE : FALSE);
}

void CLogViewerView::OnClearSelection() 
{
	CListCtrl& ctl = GetListCtrl();
	POSITION   pos = ctl.GetFirstSelectedItemPosition();
	int		   index;

	while (pos != POSITION(NULL))
	{
		index = ctl.GetNextSelectedItem(pos);
		ctl.SetItemState(index, 0, LVIS_SELECTED);
	}
}

void CLogViewerView::OnUpdateClearSelection(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(GetListCtrl().GetSelectedCount() ? TRUE : FALSE);	
}

void CLogViewerView::OnClear() 
{
	CLogViewerDoc *pDoc = GetDocument();

	ASSERT(pDoc);
	ASSERT_KINDOF(CLogViewerDoc, pDoc);

	if (AfxMessageBox(IDS_CLEARFILEWARNING, MB_YESNO) == IDYES)
		m_logFileInstance->ClearLog();
}

void CLogViewerView::OnUpdateClear(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_logFileInstance->GetSize() ? TRUE : FALSE);	
}

void CLogViewerView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView) 
{
	if (bActivate)
		//	We're being activated so now's the time to populate the
		//	filter combo boxes on the MainFrame dialog bar...
		MAINFRAME->UpdateDlgBar(m_logFileInstance->m_logFilterData);
	else
		MAINFRAME->UpdateSelections(m_logFileInstance->m_logFilterData);

	CListView::OnActivateView(bActivate, pActivateView, pDeactiveView);
	m_bIsActiveWindow = bActivate;
}

LRESULT CLogViewerView::OnAdvise(WPARAM wp, LPARAM lp)
{
	CListCtrl& ctl = GetListCtrl();
	CLogText   *pLog;

	switch (wp)
	{
	case adviseAddItem:
		pLog = (CLogText *) lp;

		ASSERT(pLog);
		ASSERT_KINDOF(CLogText, pLog);

		if (pLog->IncludeThis(m_logFileInstance->m_logFilterData))
			ctl.InsertItem(LVIF_IMAGE | LVIF_PARAM, INT_MAX, LPSTR_TEXTCALLBACK, 0, 0, pLog->Image(), LPARAM(pLog));

		break;

	case adviseEndUpdate:
		if (ctl.GetSelectedCount() == 0)
			ctl.EnsureVisible(ctl.GetItemCount() - 1, FALSE);

		ctl.SetColumnWidth(LOGVIEWER::COLUMN_DATE, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_SEQUENCE, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_CODE, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_MODULE, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_THREAD, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_MACHINE, LVSCW_AUTOSIZE_USEHEADER);
		ctl.SetColumnWidth(LOGVIEWER::COLUMN_MESSAGE, LVSCW_AUTOSIZE_USEHEADER);

		if (m_bIsActiveWindow)
			MAINFRAME->UpdateDlgBar(m_logFileInstance->m_logFilterData);
		
		break;

	case adviseClearList:
		m_logFileInstance->RemoveAll();
		ctl.DeleteAllItems();
		break;
	}

	return 0;
}

void CLogViewerView::OnSelChange()
{
	MAINFRAME->UpdateFilter(m_logFileInstance->m_logFilterData);
	GetListCtrl().DeleteAllItems();
	m_logFileInstance->Reload();
}

CLogData *CLogViewerView::ItemFromPoint(CPoint& point) const
{
	LVITEM	   item;
	UINT	   flags;
	CListCtrl& ctl = GetListCtrl();

	memset(&item, 0, sizeof(item));

	if ((item.iItem = ctl.HitTest(point, &flags)) != -1)
	{
		item.mask = LVIF_PARAM;
		ctl.GetItem(&item);
		return (CLogData *) item.lParam;
	}

	return (CLogData *) NULL;
}

BOOL CLogViewerView::OnToolTipText(UINT /*id*/, NMHDR * pNMHDR, LRESULT * pResult)
{
	USES_CONVERSION;

	// need to handle both ANSI and UNICODE versions of the message
	TOOLTIPTEXTA *pTTTA = (TOOLTIPTEXTA *) pNMHDR;
	TOOLTIPTEXTW *pTTTW = (TOOLTIPTEXTW *) pNMHDR;
	CLogText	 *pLog;
	CPoint		 lPoint;

	if (pTTTA->hdr.idFrom == 0)
		return TRUE;

	GetCursorPos(&lPoint);
	ScreenToClient(&lPoint);
	
	if ((pLog = ItemFromPoint(lPoint)) != (CLogData *) NULL)
	{
		ASSERT_VALID(pLog);
		ASSERT_KINDOF(CLogText, pLog);

		pLog->FormatForTip(m_tipText, sizeof(m_tipText), m_logFileInstance->m_logFilterData);
		
		if (pNMHDR->code == TTN_NEEDTEXTA)
			pTTTA->lpszText = m_tipText;
		else if (pNMHDR->code == TTN_NEEDTEXTW)
			pTTTW->lpszText = A2W(m_tipText);
	}
	else
		m_tip.Pop();

	*pResult = 0;
	return TRUE;    // message was handled
}

BOOL CLogViewerView::PreTranslateMessage(MSG* pMsg) 
{
	m_tip.RelayEvent(pMsg);
	return CListView::PreTranslateMessage(pMsg);
}

void CLogViewerView::OnMouseMove(UINT nFlags, CPoint point) 
{
	CLogData *pLog = ItemFromPoint(point);
	
	CListView::OnMouseMove(nFlags, point);

	if (pLog != m_lastTip || pLog == (CLogData *) NULL)
	{
		CRect rect;

		GetClientRect(&rect);
		m_tip.DelTool(this, 1);
		m_tip.AddTool(this, LPSTR_TEXTCALLBACK, &rect, 1);
		m_tip.Activate(TRUE);
		m_tip.SetDelayTime(TTDT_AUTOPOP, INT_MAX);
		m_lastTip = pLog;
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions