// XTimerDlg.cpp Version 1.3 - article available at http://www.codeproject.com/tools/xtimer.asp
//
// Author: Hans Dietrich
// hdietrich@gmail.com
//
// History
// Version 1.3 - 2007 August 11
// - Save program options in registry
// - Left doubleclick on time display opens options dialog
// - Added Quick Timer menu option
// - Added VS2005 project
//
// Version 1.2 - 2003 June 16
// - Fixed display bug reported by Pit M.
//
// Version 1.1 - 2003 June 14
// - Added "Play once, Play until stopped" option
//
// Version 1.0 - 2003 June 1
// - 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 "XTimer.h"
#include "XTimerDlg.h"
#include "TimerOptions.h"
#include <io.h>
#include "mmsystem.h"
#include "about.h"
#pragma comment(lib, "Winmm.lib")
#pragma warning(disable : 4996) // disable bogus deprecation warning
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
const TCHAR * TIME_DISPLAY_FORMAT = _T("%H:%M:%S.%s");
const int ONE_MINUTE = 60; // seconds
const int ONE_HOUR = 3600; // seconds
//=============================================================================
BEGIN_MESSAGE_MAP(CXTimerDlg, CDialog)
//=============================================================================
//{{AFX_MSG_MAP(CXTimerDlg)
ON_WM_CONTEXTMENU()
ON_WM_DESTROY()
ON_WM_LBUTTONDBLCLK()
ON_WM_NCLBUTTONDBLCLK()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_SYSCOMMAND()
ON_WM_TIMER()
ON_BN_CLICKED(IDC_MORE, OnMore)
ON_BN_CLICKED(IDC_RESET, OnReset)
ON_BN_CLICKED(IDC_START, OnStart)
ON_COMMAND(ID_ABOUT, OnAbout)
ON_COMMAND(ID_START, OnStart)
ON_COMMAND(ID_STOP, OnStop)
ON_COMMAND(ID_STOPWATCH, OnStopwatch)
ON_COMMAND(ID_TIMER, OnXTimer)
ON_COMMAND(ID_TIMER_OPTIONS, OnTimerOptions)
//}}AFX_MSG_MAP
ON_COMMAND_RANGE(ID_ONE_MINUTE, ID_EIGHT_HOURS, OnQuickTimer)
END_MESSAGE_MAP()
//=============================================================================
CXTimerDlg::CXTimerDlg(CWnd* pParent /*=NULL*/)
: CDialog(CXTimerDlg::IDD, pParent)
//=============================================================================
{
//{{AFX_DATA_INIT(CXTimerDlg)
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_bTimerStarted = FALSE;
m_nTimerSeconds = 60*10; // 10 minutes
m_bTimer = TRUE;
m_bPlaySound = TRUE;
m_bContinuousPlay = FALSE;
m_nPlayOnce = 0;
m_strSoundFile = _T("");
m_TimeLeft.SetHighTimeSpan(0, 0, 0, m_nTimerSeconds, 0, 0, 0); // for timer
m_TimeElapsed.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0); // for stopwatch
}
//=============================================================================
void CXTimerDlg::DoDataExchange(CDataExchange* pDX)
//=============================================================================
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CXTimerDlg)
DDX_Control(pDX, IDC_TIME, m_Time);
DDX_Control(pDX, IDC_START, m_btnStart);
DDX_Control(pDX, IDC_RESET, m_btnReset);
DDX_Control(pDX, IDC_MORE, m_btnMore);
//}}AFX_DATA_MAP
}
//=============================================================================
BOOL CXTimerDlg::OnInitDialog()
//=============================================================================
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
m_brush.CreateStockObject(BLACK_BRUSH);
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
LoadProfile();
SetWindowText();
RestoreWindowPos(m_hWnd, _T("position"));
SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
m_Time.SetDraw3DBar(FALSE);
short shBtnColor = 30;
m_btnStart.SetIcon(IDI_START, (int)BTNST_AUTO_GRAY);
m_btnStart.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
m_btnStart.SetTooltipText(_T("Start the timer"));
m_btnReset.SetIcon(IDI_RESET, (int)BTNST_AUTO_GRAY);
m_btnReset.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
m_btnReset.SetTooltipText(_T("Reset the timer"));
m_btnMore.SetIcon(IDI_MORE, (int)BTNST_AUTO_GRAY);
m_btnMore.OffsetColor(CButtonST::BTNST_COLOR_BK_IN, shBtnColor);
m_btnMore.SetTooltipText(_T("Display more options"));
OnReset();
return TRUE; // return TRUE unless you set the focus to a control
}
//=============================================================================
void CXTimerDlg::SetWindowText()
//=============================================================================
{
TCHAR *s = _T("");
if (m_bTimer && m_nPlayOnce)
s = _T("XTimer [TIMER+]");
else if (m_bTimer)
s = _T("XTimer [TIMER]");
else
s = _T("XTimer [STOPWATCH]");
CDialog::SetWindowText(s);
}
//=============================================================================
void CXTimerDlg::LoadProfile()
//=============================================================================
{
m_nTimerSeconds = theApp.GetProfileInt(_T("Options"), _T("Seconds"), 60*10);
m_bTimer = theApp.GetProfileInt(_T("Options"), _T("Timer"), FALSE);
m_strSoundFile = theApp.GetProfileString(_T("Options"), _T("SoundFile"), _T(""));
m_bPlaySound = theApp.GetProfileInt(_T("Options"), _T("PlaySound"), TRUE);
m_nPlayOnce = theApp.GetProfileInt(_T("Options"), _T("PlayOnce"), 0);
}
//=============================================================================
void CXTimerDlg::SaveProfile()
//=============================================================================
{
theApp.WriteProfileInt(_T("Options"), _T("Seconds"), m_nTimerSeconds);
theApp.WriteProfileInt(_T("Options"), _T("Timer"), m_bTimer);
theApp.WriteProfileString(_T("Options"), _T("SoundFile"), m_strSoundFile);
theApp.WriteProfileInt(_T("Options"), _T("PlaySound"), m_bPlaySound);
theApp.WriteProfileInt(_T("Options"), _T("PlayOnce"), m_nPlayOnce);
}
//=============================================================================
void CXTimerDlg::SaveWindowPos(HWND hWnd, LPCTSTR lpszEntry, UINT nShowCmd)
//=============================================================================
{
ASSERT(hWnd != 0);
if (hWnd == 0)
return;
ASSERT(::IsWindow(hWnd));
if (!::IsWindow(hWnd))
return;
ASSERT(lpszEntry != NULL);
if (lpszEntry == NULL)
return;
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
// get window position and iconized/maximized status
::GetWindowPlacement(hWnd, &wp);
if (wp.showCmd == SW_SHOWNORMAL)
{
wp.flags = 0;
wp.showCmd = nShowCmd;
}
else if (wp.showCmd == SW_SHOWMAXIMIZED)
{
wp.flags = WPF_RESTORETOMAXIMIZED;
wp.showCmd = SW_SHOWMAXIMIZED;
}
else if (wp.showCmd == SW_SHOWMINIMIZED)
{
wp.flags = wp.flags ? WPF_RESTORETOMAXIMIZED : 0;
wp.showCmd = SW_SHOWMINNOACTIVE;
}
else
{
wp.flags = 0;
wp.showCmd = SW_SHOWNORMAL;
}
TCHAR tmp[100];
_stprintf(tmp, _T("%u,%u,%u,%u,%u,%u"),
wp.flags,
wp.showCmd,
wp.rcNormalPosition.left,
wp.rcNormalPosition.top,
wp.rcNormalPosition.right,
wp.rcNormalPosition.bottom);
// save position
theApp.WriteProfileString(_T("Window"), lpszEntry, tmp);
}
//=============================================================================
void CXTimerDlg::RestoreWindowPos(HWND hWnd, LPCTSTR lpszEntry)
//=============================================================================
{
ASSERT(hWnd != 0);
if (hWnd == 0)
return;
ASSERT(::IsWindow(hWnd));
if (!::IsWindow(hWnd))
return;
ASSERT(lpszEntry != NULL);
if (lpszEntry == NULL)
return;
// read window position
CString s = _T("");
s = theApp.GetProfileString(_T("Window"), lpszEntry,
_T("0,1,400,400,400,400"));
TCHAR tmp[200];
_tcsncpy(tmp, s, sizeof(tmp)/sizeof(TCHAR)-1);
// get WINDOWPLACEMENT info
WINDOWPLACEMENT wp;
memset(&wp, 0, sizeof(wp));
wp.length = sizeof(WINDOWPLACEMENT);
wp.rcNormalPosition.left = 0;
wp.rcNormalPosition.top = 0;
wp.ptMinPosition = CPoint(0, 0);
wp.ptMaxPosition = CPoint(-::GetSystemMetrics(SM_CXBORDER),
-::GetSystemMetrics(SM_CYBORDER));
CRect rect;
GetWindowRect(&rect);
TCHAR *cp;
if ((cp = _tcstok(tmp, _T(",\r\n"))) != NULL)
wp.flags = _ttoi(cp);
if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
wp.showCmd = _ttoi(cp);
if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
wp.rcNormalPosition.left = _ttoi(cp);
if ((cp = _tcstok(NULL, _T(",\r\n"))) != NULL)
wp.rcNormalPosition.top = _ttoi(cp);
wp.rcNormalPosition.right = wp.rcNormalPosition.left + rect.Width();
wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rect.Height();
// sets window's position and iconized/maximized status
::SetWindowPlacement(hWnd, &wp);
}
//=============================================================================
void CXTimerDlg::OnSysCommand(UINT nID, LPARAM lParam)
//=============================================================================
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
//=============================================================================
void CXTimerDlg::OnPaint()
//=============================================================================
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
//=============================================================================
HCURSOR CXTimerDlg::OnQueryDragIcon()
//=============================================================================
{
return (HCURSOR) m_hIcon;
}
//=============================================================================
void CXTimerDlg::OnNcLButtonDblClk(UINT /*nHitTest*/, CPoint /*point*/)
//=============================================================================
{
// disable double-click in caption - disallow full-screen
//CDialog::OnNcLButtonDblClk(nHitTest, point);
}
//=============================================================================
void CXTimerDlg::OnTimer(UINT nIDEvent)
//=============================================================================
{
if (nIDEvent == 1)
{
CHighTime curtime = CHighTime::GetPresentTime();
CHighTimeSpan ts = curtime - m_StartTime;
CString s = _T("");
if (m_bTimer)
{
m_TimeLeft -= ts;
s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);
if (m_TimeLeft < CHighTimeSpan(0, 0, 0, 0, 100, 0, 0))
{
if (m_nPlayOnce)
{
TRACE(_T("switching to continuous play"));
KillTimer(1);
SetTimer(2, 30000, NULL); // play sound every 30 seconds
// until stopped
m_bContinuousPlay = TRUE;
m_TimeLeft.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0); // for timer
CString s = _T("");
s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);
m_Time.Display(s);
m_Time.UpdateWindow();
PlaySoundFile();
return;
}
OnStop();
OnReset();
PlaySoundFile();
return;
}
}
else
{
m_TimeElapsed += ts;
s = m_TimeElapsed.Format(TIME_DISPLAY_FORMAT);
}
m_StartTime = curtime;
m_Time.Display(s);
m_Time.UpdateWindow();
}
else if (nIDEvent == 2)
{
PlaySoundFile();
}
}
//=============================================================================
void CXTimerDlg::LoadMenu(CPoint point)
//=============================================================================
{
CMenu menu;
if (!m_bTimerStarted)
{
if (!menu.LoadMenu(IDR_CONTEXT1))
{
TRACE(_T("ERROR failed to load IDR_CONTEXT1\n"));
return;
}
menu.CheckMenuRadioItem(ID_STOPWATCH, ID_TIMER,
m_bTimer ? ID_TIMER : ID_STOPWATCH, MF_BYCOMMAND);
}
else
{
if (!menu.LoadMenu(IDR_CONTEXT2))
{
TRACE(_T("ERROR failed to load IDR_CONTEXT2\n"));
return;
}
menu.CheckMenuRadioItem(ID_STOPWATCH, ID_TIMER,
m_bTimer ? ID_TIMER : ID_STOPWATCH, MF_BYCOMMAND);
}
menu.GetSubMenu(0)->TrackPopupMenu(0,
point.x, point.y, this, NULL);
}
//=============================================================================
void CXTimerDlg::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
//=============================================================================
{
LoadMenu(point);
SetWindowText();
}
//=============================================================================
void CXTimerDlg::OnStart()
//=============================================================================
{
if (m_bTimerStarted)
{
OnStop();
return;
}
m_bTimerStarted = TRUE;
m_btnStart.SetIcon(IDI_STOP, (int)BTNST_AUTO_GRAY);
m_btnStart.SetWindowText(_T("Stop"));
m_btnStart.SetTooltipText(_T("Stop the timer"));
m_StartTime = CHighTime::GetPresentTime();
SetTimer(1, 100, NULL);
}
//=============================================================================
void CXTimerDlg::OnStop()
//=============================================================================
{
KillTimer(1);
KillTimer(2);
m_bTimerStarted = FALSE;
if (m_bContinuousPlay)
Reset();
m_bContinuousPlay = FALSE;
m_btnStart.SetIcon(IDI_START, (int)BTNST_AUTO_GRAY);
m_btnStart.SetWindowText(_T("Start"));
m_btnStart.SetTooltipText(_T("Start the timer"));
}
//=============================================================================
void CXTimerDlg::Reset()
//=============================================================================
{
m_TimeLeft.SetHighTimeSpan(0, 0, 0, m_nTimerSeconds, 0, 0, 0); // for timer
m_TimeElapsed.SetHighTimeSpan(0, 0, 0, 0, 0, 0, 0); // for stopwatch
CString s = _T("");
if (m_bTimer)
s = m_TimeLeft.Format(TIME_DISPLAY_FORMAT);
else
s = m_TimeElapsed.Format(TIME_DISPLAY_FORMAT);
m_Time.Display(s);
m_Time.UpdateWindow();
}
//=============================================================================
void CXTimerDlg::OnReset()
//=============================================================================
{
OnStop();
Reset();
}
//=============================================================================
void CXTimerDlg::OnMore()
//=============================================================================
{
CRect rectButton;
m_btnMore.GetWindowRect(&rectButton);
CPoint point(rectButton.left, rectButton.bottom);
LoadMenu(point);
}
//=============================================================================
void CXTimerDlg::OnXTimer()
//=============================================================================
{
m_bTimer = TRUE;
SetWindowText();
OnReset();
}
//=============================================================================
void CXTimerDlg::OnStopwatch()
//=============================================================================
{
m_bTimer = FALSE;
SetWindowText();
OnReset();
}
//=============================================================================
void CXTimerDlg::OnTimerOptions()
//=============================================================================
{
OnStop();
CTimerOptions dlg;
dlg.m_bPlaySound = m_bPlaySound;
dlg.m_nPlayOnce = m_nPlayOnce;
dlg.m_strSoundFile = m_strSoundFile;
int nSeconds = m_nTimerSeconds;
dlg.m_nHours = nSeconds / (60 * 60);
nSeconds -= dlg.m_nHours * 60 * 60;
dlg.m_nMinutes = nSeconds / 60;
nSeconds -= dlg.m_nMinutes * 60;
dlg.m_nSeconds = nSeconds;
if (dlg.DoModal() == IDOK)
{
m_bPlaySound = dlg.m_bPlaySound;
m_nPlayOnce = dlg.m_nPlayOnce;
m_strSoundFile = dlg.m_strSoundFile;
m_nTimerSeconds = dlg.m_nHours * 60 * 60 +
dlg.m_nMinutes * 60 +
dlg.m_nSeconds;
SetWindowText();
}
OnReset();
}
//=============================================================================
void CXTimerDlg::OnAbout()
//=============================================================================
{
CAboutDlg dlg;
dlg.DoModal();
}
//=============================================================================
void CXTimerDlg::PlaySoundFile()
//=============================================================================
{
FLASHWINFO fwi;
fwi.cbSize = sizeof(fwi);
fwi.hwnd = m_hWnd;
fwi.dwFlags = FLASHW_ALL | FLASHW_TIMER;
fwi.uCount = 5;
fwi.dwTimeout = 500;
::FlashWindowEx(&fwi);
if (m_bPlaySound)
{
if (_taccess(m_strSoundFile, 04) == 0)
{
::PlaySound(m_strSoundFile, AfxGetApp()->m_hInstance,
SND_FILENAME|SND_ASYNC|SND_NOWAIT|SND_NODEFAULT);
}
else
{
TRACE(_T("The sound file '%s' cannot be played, playing default sound\n"),
m_strSoundFile);
::PlaySound(_T("IDR_DEFAULTSOUND"), AfxGetApp()->m_hInstance,
SND_RESOURCE|SND_ASYNC|SND_NOWAIT|SND_NODEFAULT);
}
}
}
//=============================================================================
void CXTimerDlg::OnDestroy()
//=============================================================================
{
SaveProfile();
SaveWindowPos(m_hWnd, _T("position"), SW_SHOW);
CDialog::OnDestroy();
}
//=============================================================================
void CXTimerDlg::OnLButtonDblClk(UINT nFlags, CPoint point)
//=============================================================================
{
OnTimerOptions();
CDialog::OnLButtonDblClk(nFlags, point);
}
//=============================================================================
void CXTimerDlg::OnQuickTimer(UINT nID)
//=============================================================================
{
switch (nID)
{
default:
case ID_ONE_MINUTE: m_nTimerSeconds = 1 * ONE_MINUTE; break;
case ID_TWO_MINUTES: m_nTimerSeconds = 2 * ONE_MINUTE; break;
case ID_FIVE_MINUTES: m_nTimerSeconds = 5 * ONE_MINUTE; break;
case ID_TEN_MINUTES: m_nTimerSeconds = 10 * ONE_MINUTE; break;
case ID_FIFTEEN_MINUTES: m_nTimerSeconds = 15 * ONE_MINUTE; break;
case ID_TWENTY_MINUTES: m_nTimerSeconds = 20 * ONE_MINUTE; break;
case ID_THIRTY_MINUTES: m_nTimerSeconds = 30 * ONE_MINUTE; break;
case ID_FORTYFIVE_MINUTES: m_nTimerSeconds = 45 * ONE_MINUTE; break;
case ID_ONE_HOUR: m_nTimerSeconds = 1 * ONE_HOUR; break;
case ID_TWO_HOURS: m_nTimerSeconds = 2 * ONE_HOUR; break;
case ID_THREE_HOURS: m_nTimerSeconds = 3 * ONE_HOUR; break;
case ID_FOUR_HOURS: m_nTimerSeconds = 4 * ONE_HOUR; break;
case ID_FIVE_HOURS: m_nTimerSeconds = 5 * ONE_HOUR; break;
case ID_SIX_HOURS: m_nTimerSeconds = 6 * ONE_HOUR; break;
case ID_SEVEN_HOURS: m_nTimerSeconds = 7 * ONE_HOUR; break;
case ID_EIGHT_HOURS: m_nTimerSeconds = 8 * ONE_HOUR; break;
}
m_bTimer = TRUE;
SetWindowText();
OnReset();
OnStart();
}