Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

XHTMLTipOfTheDay - HTML Tip of the Day Dialog

, 14 Jun 2004 CPOL
XHTMLTipOfTheDay is a Tip of the Day dialog that displays HTML and has forward and back buttons.
// XHTMLTipOfTheDayDlg.cpp  Version 1.0
//
// Author:  Hans Dietrich
//          hdietrich2@hotmail.com
//
// History
//     Version 1.0 - 2004 June 15
//     - Initial public release
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this 
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XHTMLTipOfTheDayDlg.h"
#include "XHTMLTipOfTheDayRes.h"
#include <sys\stat.h>
#include <sys\types.h>
#include "Shlwapi.h."		// for PathCompactPath()

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

#pragma message("automatic link to SHLWAPI.LIB")
#pragma comment(lib, "Shlwapi.lib")


#define MAX_BUFLEN 10000

static const TCHAR szSection[]    = _T("TipOfTheDay");
static const TCHAR szIntTipNo[]   = _T("TipNo");
static const TCHAR szTimeStamp[]  = _T("TimeStamp");
static const TCHAR szIntStartup[] = _T("StartUp");


///////////////////////////////////////////////////////////////////////////////
// CXHTMLTipOfTheDayDlg dialog

BEGIN_MESSAGE_MAP(CXHTMLTipOfTheDayDlg, CDialog)
	//{{AFX_MSG_MAP(CXHTMLTipOfTheDayDlg)
	ON_BN_CLICKED(IDC_NEXTTIP, OnNextTip)
	ON_WM_CTLCOLOR()
	ON_WM_PAINT()
	ON_BN_CLICKED(IDC_PREVTIP, OnPrevTip)
	ON_WM_WINDOWPOSCHANGING()
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXHTMLTipOfTheDayDlg::CXHTMLTipOfTheDayDlg(LPCTSTR lpszTipFile, CWnd* pParent /*=NULL*/)
	: CDialog(_T("TIP_OF_THE_DAY"), pParent)
{
	//{{AFX_DATA_INIT(CXHTMLTipOfTheDayDlg)
	//}}AFX_DATA_INIT
	m_bVisible   = TRUE;
	m_pStream    = NULL;
	m_nTipNo     = 0;
	m_nLastTipNo = 0;
	ASSERT(lpszTipFile);
	m_strTipFile = lpszTipFile;
	m_SideBrush.CreateSolidBrush(::GetSysColor(COLOR_BTNSHADOW));

	CWinApp* pApp = AfxGetApp();
	m_bStartup = pApp->GetProfileInt(szSection, szIntStartup, TRUE);
}

///////////////////////////////////////////////////////////////////////////////
// dtor
CXHTMLTipOfTheDayDlg::~CXHTMLTipOfTheDayDlg()
{
	if (m_SideBrush.GetSafeHandle())
		m_SideBrush.DeleteObject();
}
  
///////////////////////////////////////////////////////////////////////////////
// DoDataExchange
void CXHTMLTipOfTheDayDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CXHTMLTipOfTheDayDlg)
	DDX_Control(pDX, IDC_NEXTTIP,   m_btnNext);
	DDX_Control(pDX, IDC_PREVTIP,   m_btnPrev);
	DDX_Control(pDX, IDC_SIDE,      m_Side);
	DDX_Control(pDX, IDC_HEADER,    m_Header);
	DDX_Control(pDX, IDC_TIPSTRING, m_Tip);
	DDX_Control(pDX, IDC_STARTUP,   m_chkStartup);
	//}}AFX_DATA_MAP
}

///////////////////////////////////////////////////////////////////////////////
// OnInitDialog
BOOL CXHTMLTipOfTheDayDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// set up glyph buttons
	m_btnPrev.SetGlyph(16, _T("Wingdings"), 0xEF);
	m_btnNext.SetGlyph(16, _T("Wingdings"), 0xF0);

	if (m_bStartup)
		m_bVisible = TRUE;		// OK to show
	else
		m_bVisible = FALSE;		// user doesn't want to see - hide the window
								// until OnWindowPosChanging() can send WM_CLOSE

	m_chkStartup.SetCheck(m_bStartup);

	m_nTipNo = AfxGetApp()->GetProfileInt(szSection, szIntTipNo, 0);
	if (m_nTipNo > 0)
		m_nTipNo++;

	// get path for tips file =============================

	TCHAR szPath[_MAX_PATH*3] = { 0 };
	HINSTANCE hinst = AfxGetInstanceHandle();
	::GetModuleFileName(hinst, szPath, sizeof(szPath)/sizeof(TCHAR)-2);
	TCHAR * cp = _tcsrchr(szPath, _T('\\'));
	if (cp)
		*cp = _T('\0');

	CString strTipPath(szPath);
	strTipPath += _T("\\");

	if (!m_strTipFile.IsEmpty())
		strTipPath += m_strTipFile;
	else
		strTipPath += _T("Tips.txt");

	CString strStoredTime = _T("");
	CString strModifyTime = _T("");

	BOOL bFirstTime = FALSE;

	// open tips file =====================================

	m_pStream = _tfopen(strTipPath, _T("r"));
	if (m_pStream == NULL) 
	{
		// can't open tips file - display filepath
		CString str = _T("");;
		str.LoadString(IDS_TIP_FILE_MISSING);
		strTipPath.MakeUpper();
		LPTSTR lpch = strTipPath.GetBuffer(MAX_PATH);
		CClientDC dc(this);
		CRect rectClient;
		GetDlgItem(IDC_TIPSTRING)->GetClientRect(&rectClient);

		// get shortened name that will fit in client rect
		if (::PathCompactPath(dc.m_hDC, lpch, rectClient.Width()-4))
			m_strTip.Format(str, lpch);
		else
			m_strTip.Format(str, strTipPath);
		strTipPath.ReleaseBuffer();
	}
	else
	{
		// tips file opened ok

		// If the saved timestamp is different from the current timestamp of
		// the tips file, then we know that the tips file has been modified.
		// Reset the file position to 0 and save the latest timestamp.

		struct _stat buf;
		_fstat(_fileno(m_pStream), &buf);
		strModifyTime = ctime(&buf.st_mtime);
		strModifyTime.TrimRight();

		strStoredTime = AfxGetApp()->GetProfileString(szSection, szTimeStamp);

		AfxGetApp()->WriteProfileString(szSection, szTimeStamp, strModifyTime);

		// tip file was modified, start at first tip
		if (!strStoredTime.IsEmpty())
		{
			if (strModifyTime != strStoredTime)
			{
				m_nTipNo = 1;
			}
		}
		else
		{
			// this is the first time that a tip is being displayed
			bFirstTime = TRUE;
			m_nTipNo = 0;
		}

		m_nLastTipNo = GetLastTipNo();

		GetTipString(&m_nTipNo, m_strTip, bFirstTime);

		// if Tips file does not exist then disable NextTip
		if (m_pStream == NULL)
		{
			GetDlgItem(IDC_NEXTTIP)->EnableWindow(FALSE);
			GetDlgItem(IDC_PREVTIP)->EnableWindow(FALSE);
		}
	}

	// display the header =================================
	m_Header.SetMargins(10, 10);
	int nHeader = 0;
	if (!strStoredTime.IsEmpty())
		nHeader = 1;
	DisplayHeader(nHeader);

	// display the tip ====================================
	m_Tip.SetMargins(10, 20);

	// set font a bit larger
	LOGFONT lf;
	CFont* pFont = GetFont();
	if (pFont)
		pFont->GetObject(sizeof(lf), &lf);
	else
		GetObject(GetStockObject(SYSTEM_FONT), sizeof(lf), &lf);
	if (lf.lfHeight < 0)
		lf.lfHeight -= 2;
	else
		lf.lfHeight += 2;
	m_Tip.SetLogFont(&lf);

	m_Tip.SetWindowText(m_strTip);

	return TRUE;  // return TRUE unless you set the focus to a control
}

///////////////////////////////////////////////////////////////////////////////
// OnNextTip
void CXHTMLTipOfTheDayDlg::OnNextTip()
{
	if (m_nTipNo == 0)
		DisplayHeader(1);
	m_nTipNo++;
	if (m_nTipNo > m_nLastTipNo)
		m_nTipNo = 0;
	if (GetTipString(&m_nTipNo, m_strTip, FALSE))
		m_Tip.SetWindowText(m_strTip);
}

///////////////////////////////////////////////////////////////////////////////
// OnPrevTip
void CXHTMLTipOfTheDayDlg::OnPrevTip() 
{
	if (m_nTipNo == 0)
		DisplayHeader(1);
	m_nTipNo--;
	if (m_nTipNo <= 0)
		m_nTipNo = m_nLastTipNo;
	if (GetTipString(&m_nTipNo, m_strTip, FALSE))
		m_Tip.SetWindowText(m_strTip);
}

///////////////////////////////////////////////////////////////////////////////
// DisplayHeader
void CXHTMLTipOfTheDayDlg::DisplayHeader(int nHeader) 
{
	static const TCHAR * szHeader = 
	{
		_T("<br>")
		_T("<b><font size=\"+8\" face=\"Times New Roman\">%s</font></b>")
		_T("<br><font color=\"%d,%d,%d\"><hr size=1></font>")
	};
	
	CString strText = _T("");

	if (nHeader == 0)
		VERIFY(strText.LoadString(IDS_WELCOME));
	else
		VERIFY(strText.LoadString(IDS_DIDYOUKNOW));

	// format the header with the rgb color for the rule
	CString strHeader = _T("");
	COLORREF rgb = ::GetSysColor(COLOR_BTNSHADOW);
	strHeader.Format(szHeader, strText, 
		GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
	m_Header.SetWindowText(strHeader);
}

///////////////////////////////////////////////////////////////////////////////
// GetTipLine
TCHAR * CXHTMLTipOfTheDayDlg::GetTipLine(TCHAR *buffer, int n)
{
	ASSERT(buffer);

	while (_fgetts(buffer, n-1, m_pStream) != NULL)
	{
		if (*buffer != _T(' ')  && *buffer != _T('\t') && 
			*buffer != _T('\n') && *buffer != _T(';')) 
		{
			// There should be no space at the beginning of the tip
			// This behavior is same as VC++ Tips file
			// Comment lines are ignored and they start with a semicolon
			return buffer;
		}
	}

	return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// GetLastTipNo
int CXHTMLTipOfTheDayDlg::GetLastTipNo()
{
	int nTipNo = 0;

	if (m_pStream)
	{
		CString str = _T("");
		LPTSTR lpsz = str.GetBuffer(MAX_BUFLEN);
		*lpsz = _T('\0');

		fseek(m_pStream, 0, SEEK_SET);	// rewind file

		for ( ; ; nTipNo++)
		{
			if (GetTipLine(lpsz, MAX_BUFLEN-1) == NULL) 
				break;
		}
		if (nTipNo > 0)
			nTipNo--;
	}

	return nTipNo;
}

///////////////////////////////////////////////////////////////////////////////
// GetTipString
BOOL CXHTMLTipOfTheDayDlg::GetTipString(int* pnTip, CString& str, BOOL bFirstTime)
{
	if (!bFirstTime && (*pnTip < 1))
		*pnTip = 1;		// don't show welcome tip again

	LPTSTR lpsz = str.GetBuffer(MAX_BUFLEN);
	*lpsz = _T('\0');

	if (m_pStream)
	{
		fseek(m_pStream, 0, SEEK_SET);	// rewind file
		int n = 0;

		do
		{
			if (GetTipLine(lpsz, MAX_BUFLEN-1) == NULL) 
			{
				// We have either reached EOF or enocuntered some problem
				// In both cases reset the pointer to the beginning of the file
				// This behavior is same as VC++ Tips file
				fseek(m_pStream, 0, SEEK_SET);	// rewind file

				*pnTip = 1;

				// skip the welcome tip
				if (GetTipLine(lpsz, MAX_BUFLEN-1) != NULL) 
				{
					// start at second tip
					GetTipLine(lpsz, MAX_BUFLEN-1);
				}
				break;
			} 
			else 
			{
				// There should be no space at the beginning of the tip
				// This behavior is same as VC++ Tips file
				// Comment lines are ignored and they start with a semicolon
				n++;
			}

		}  while (n <= *pnTip);
	}

	str.ReleaseBuffer();

	return !str.IsEmpty();
}

///////////////////////////////////////////////////////////////////////////////
// OnCtlColor
HBRUSH CXHTMLTipOfTheDayDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	if (pWnd && ::IsWindow(pWnd->m_hWnd))
	{
		if (pWnd->m_hWnd == m_Tip.m_hWnd)
			return (HBRUSH)GetStockObject(WHITE_BRUSH);
		if (pWnd->m_hWnd == m_Header.m_hWnd)
			return (HBRUSH)GetStockObject(WHITE_BRUSH);
		if (pWnd->m_hWnd == m_Side.m_hWnd)
			return (HBRUSH)GetStockObject(NULL_BRUSH);
	}

	return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}

///////////////////////////////////////////////////////////////////////////////
// OnPaint
void CXHTMLTipOfTheDayDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting

	DoPaint(&dc);

	// Do not call CDialog::OnPaint() for painting messages
}

///////////////////////////////////////////////////////////////////////////////
// DoPaint
void CXHTMLTipOfTheDayDlg::DoPaint(CDC *pdc)
{
	CWnd* pStatic;
	CRect  rectBulb, rectSide, rectHeader;

	pStatic = GetDlgItem(IDC_LIGHTBULB);
	if (!pStatic)
	{
		ASSERT(FALSE);
		return;
	}
	pStatic->GetWindowRect(rectBulb);
	ScreenToClient(rectBulb);
	pStatic->ShowWindow(SW_HIDE);

	pStatic = GetDlgItem(IDC_SIDE);
	if (!pStatic)
	{
		ASSERT(FALSE);
		return;
	}
	pStatic->GetWindowRect(rectSide);
	ScreenToClient(rectSide);
	pStatic->ShowWindow(SW_HIDE);

	pStatic = GetDlgItem(IDC_HEADER);
	if (!pStatic)
	{
		ASSERT(FALSE);
		return;
	}
	pStatic->GetWindowRect(rectHeader);
	ScreenToClient(rectHeader);

	// draw border
	CRect rectBorder(rectSide.left-2, rectHeader.top-2, 
		rectHeader.right+2, rectSide.bottom+2);
	pdc->DrawEdge(&rectBorder, EDGE_SUNKEN, BF_RECT);

	// paint left side button shadow color
	pdc->FillSolidRect(rectSide, ::GetSysColor(COLOR_BTNSHADOW));	
	pdc->SetBkColor(::GetSysColor(COLOR_BTNSHADOW));

	// load bitmap and get dimensions of the bitmap
	CBitmap bmp;
	VERIFY(bmp.LoadBitmap(IDB_LIGHTBULB));
	BITMAP bmpInfo;	
	bmp.GetBitmap(&bmpInfo);

	// draw bitmap in top corner and validate only top portion of window
	CDC dcTmp;
	dcTmp.CreateCompatibleDC(pdc);
	dcTmp.SelectObject(&bmp);
	rectBulb.bottom = bmpInfo.bmHeight + rectBulb.top;
	rectBulb.right = bmpInfo.bmWidth + rectBulb.left;
	dcTmp.SelectObject(&m_SideBrush);
	dcTmp.ExtFloodFill(0,0, 0x00FFFFFF, FLOODFILLSURFACE);
	pdc->BitBlt(rectBulb.left, rectBulb.top, 
				rectBulb.Width(), rectBulb.Height(), 
				&dcTmp, 0, 0, SRCCOPY);
}

///////////////////////////////////////////////////////////////////////////////
// OnWindowPosChanging
void CXHTMLTipOfTheDayDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
	if (!m_bVisible)
	{
		lpwndpos->flags &= ~SWP_SHOWWINDOW ;
		SendMessage(WM_CLOSE);
	}

	CDialog::OnWindowPosChanging(lpwndpos);
}

///////////////////////////////////////////////////////////////////////////////
// OnDestroy
void CXHTMLTipOfTheDayDlg::OnDestroy() 
{
	// Update the tip number in the ini file (or registry) with
	// the latest position so we don't repeat the tips.
 
	m_bStartup = m_chkStartup.GetCheck();

	if (m_pStream) 
	{
		fclose(m_pStream);
		m_pStream = NULL;
		AfxGetApp()->WriteProfileInt(szSection, szIntTipNo, m_nTipNo);
		AfxGetApp()->WriteProfileInt(szSection, szIntStartup, m_bStartup);
	}

	CDialog::OnDestroy();
}

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)

Share

About the Author

Hans Dietrich
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.
 
Recently, I have moved to Los Angeles where I am doing consulting and development work.
 
For consulting and custom software development, please see www.hdsoft.org.






| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 14 Jun 2004
Article Copyright 2004 by Hans Dietrich
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid