Click here to Skip to main content
15,895,557 members
Articles / Programming Languages / C++

Clock Screen Saver

Rate me:
Please Sign up or sign in to vote.
4.73/5 (42 votes)
27 Jun 2004CPOL4 min read 155K   4.5K   48  
A mouse trailing clock screen saver written in MFC
This project is a screen saver application that I originally started as a way to pick up C++/MFC. In the years since, I’ve added some features to it like Outlook Calendar and MAPI support for signaling when new mail arrives while the screen saver is active.
// FullScreenClockWnd.cpp : implementation file
//

#include "stdafx.h"
#include "resource.h"		// main symbols
#include "ClockSaverWnd.h"
#include "Clock.h"
#include "ClockSettings.h"
#include "Monitors.h"
#include "Monitor.h"

#include "MailNotification.h"
#include "MailNotificationFactory.h"
#include "Reminder.h"

#define RANDOM( lbound, ubound ) (int)((ubound - lbound + 1) * ( (double)rand() / (double)RAND_MAX ) + lbound)


#define QUICKEXIT_THRESHOLD 500		//milliseconds
// CClockSaverWnd

//IMPLEMENT_DYNAMIC(CClockSaverWnd, CWnd)
CClockSaverWnd::CClockSaverWnd() : m_bTerminateOnMouse( TRUE ),
												m_pMailNotification( NULL ),
												m_hMailOpenIcon( NULL ),
												m_hMailClosedIcon( NULL )
{
	//seed the random number generator
	::srand( (unsigned)::time( NULL ) );
}

CClockSaverWnd::~CClockSaverWnd()
{
}


BEGIN_MESSAGE_MAP(CClockSaverWnd, CSaverWnd)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()

	ON_WM_SYSCOMMAND()
	ON_WM_SETCURSOR()
	ON_WM_NCACTIVATE()
	ON_WM_ACTIVATE()
	ON_WM_ACTIVATEAPP()
	ON_WM_MOUSEWHEEL( )
	ON_WM_LBUTTONDOWN()
	ON_WM_MBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_WM_KEYDOWN()
	ON_WM_SYSKEYDOWN()
END_MESSAGE_MAP()

BEGIN_TIMER_MAP( CClockSaverWnd )
	ON_TIMER( TIMER_QUICKEXIT, OnQuickExitTimer )
	ON_TIMER( TIMER_MOUSEEXIT, OnExitOnMouseTimer )
	ON_TIMER( TIMER_SHUTDOWN_COMPUTER, OnShutDownComputerTimer )
	ON_TIMER( TIMER_IDLE, OnIdleTimer )
	ON_TIMER( TIMER_BLINKMAIL, OnBlinkMailTimer )
	ON_TIMER( TIMER_WIGGLE_MOUSE, OnWiggleMouse )
	ON_TIMER( TIMER_START_WIGGLE_MOUSE, OnStartWiggleMouse )
	ON_TIMER( TIMER_END_WIGGLE_MOUSE, OnEndWiggleMouse )
END_TIMER_MAP( CSaverWnd )

BOOL CClockSaverWnd::Create()
{
	CRect rect;
	CMonitors::GetVirtualDesktopRect( &rect );

	return CSaverWnd::Create(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, ::AfxGetAppName(), WS_POPUP, rect, GetDesktopWindow());
}

// CClockSaverWnd message handlers
int CClockSaverWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	int ret = CSaverWnd::OnCreate(lpCreateStruct);

	if ( ret == 0 ) 
	{
		// if the mouse moves within QUICKEXIT_THRESHOLD of starting the screen saver will exit
		SET_TIMER( TIMER_QUICKEXIT, QUICKEXIT_THRESHOLD );

		if ( m_ClockSettings.GetShowNewMailNotification() && ConnectToMail() )
		{
			CMonitor monitor = CMonitors::GetPrimaryMonitor();
			CRect rect;
			monitor.GetMonitorRect( &rect );
			ScreenToClient( &rect );

			int ret1 = m_MailLabel.Create( _T("mail"), WS_CHILD|WS_VISIBLE|SS_ICON|SS_CENTERIMAGE, CRect(rect.right - 32, rect.bottom - 32, rect.right, rect.bottom ), this);
			if ( ret1 != 0 )
				m_MailLabel.SetIcon( ::AfxGetApp()->LoadIcon( IDI_BLACK ) );

			if ( m_pMailNotification->HasCalendar() )
				m_pMailNotification->GetAllDayEvents( m_AllDayEvents );
		}

		if ( m_ClockSettings.GetShutDownComputer() )
			SET_TIMER( TIMER_SHUTDOWN_COMPUTER, m_ClockSettings.GetShutDownComputerInterval() * 60 * 60 * 1000 );

		m_hMailOpenIcon = (HICON)::LoadImage( ::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAIL_OPEN), IMAGE_ICON, 32, 32, LR_LOADTRANSPARENT );
		m_hMailClosedIcon = (HICON)::LoadImage( ::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAIL_CLOSED), IMAGE_ICON, 32, 32, LR_LOADTRANSPARENT );
	}

	return ret;
}

void CClockSaverWnd::Draw( CDC* pDC )
{
	//then draw our reminders and all day events
	CRect rect;
	CMonitors::GetPrimaryMonitor().GetMonitorRect( &rect );

	CRect reminderRect = rect;
	reminderRect.right = rect.Width() / 2;
	ScreenToClient( &reminderRect );

	COLORREF oldColor = pDC->SetTextColor( RGB(255,0,0) );

	for ( int i = 0; i < m_RemindersArray.GetCount(); i++ )
	{
		CReminder* pReminder = (CReminder*)m_RemindersArray[i];

		if ( pReminder->IsPastDue() )
			pDC->SetTextColor( RGB(255,0,0) );
		else
			pDC->SetTextColor( RGB(255,255,255) );

		CString text;
		pReminder->GetDisplayString( text );

		reminderRect.top += pDC->DrawText( text, &reminderRect, DT_LEFT|DT_TOP|DT_WORDBREAK );
	}

	if ( m_AllDayEvents.GetLength() > 0 )
	{
		CRect AllDayEventsRect = rect;
		AllDayEventsRect.left = rect.Width() / 2;
		AllDayEventsRect.right = rect.right;
		ScreenToClient( &AllDayEventsRect );

		pDC->SetTextColor( RGB(255,255,255) );
		pDC->DrawText( m_AllDayEvents, &AllDayEventsRect, DT_RIGHT|DT_TOP|DT_WORDBREAK );
	}

	pDC->SetTextColor( oldColor );

	CSaverWnd::Draw( pDC );
}

bool CClockSaverWnd::ConnectToMail()
{
	m_pMailNotification = CMailNotificationFactory::CreateMailNotification( this );

	return m_pMailNotification != NULL;
}

BOOL CClockSaverWnd::OnWiggleMouse()
{
	CPoint clockPoint = m_pClock->GetPoint();
	CPoint point( clockPoint );
	
	point.x += RANDOM( -10, 10 );
	point.y += RANDOM( -10, 10 );

	m_pClock->SetPoint( point );

	return FALSE;
}

BOOL CClockSaverWnd::OnStartWiggleMouse()
{
	SET_TIMER( TIMER_WIGGLE_MOUSE, 500 );
	SET_TIMER( TIMER_END_WIGGLE_MOUSE, 5000 );

	return TRUE;
}

BOOL CClockSaverWnd::OnEndWiggleMouse()
{
	KILL_TIMER( TIMER_WIGGLE_MOUSE );
	SET_TIMER( TIMER_START_WIGGLE_MOUSE, RANDOM( 60000, 120000 ) );

	return TRUE;
}

void CClockSaverWnd::OnGotNewMail()
{
	SET_TIMER( TIMER_BLINKMAIL, 1000 );
}

void CClockSaverWnd::OnDestroy()
{
	CObject* pa = NULL;
	for ( int i = m_RemindersArray.GetCount(); i > 0; i-- )
	{
		if( ( pa = (CObject*)m_RemindersArray.GetAt( 0 ) ) != NULL )
		{
			m_RemindersArray.RemoveAt( 0 );  // Element 1 moves to 0.
			delete pa; // Delete the original element at 0.
		}
	}

	if ( ::IsWindow( m_MailLabel ) )
		m_MailLabel.DestroyWindow();

	delete m_pMailNotification;

	::DestroyIcon( m_hMailOpenIcon );
	::DestroyIcon( m_hMailClosedIcon );

	CSaverWnd::OnDestroy();
}

BOOL CClockSaverWnd::OnShutDownComputerTimer()
{
	ShutDownComputer();	

	return TRUE;
}

BOOL CClockSaverWnd::OnBlinkMailTimer()
{
	static HICON icon = m_hMailOpenIcon;

	if ( icon == m_hMailOpenIcon )
		icon = m_hMailClosedIcon;
	else
		icon = m_hMailOpenIcon;
	
	m_MailLabel.SetIcon( icon );

	return FALSE;
}

BOOL CClockSaverWnd::OnExitOnMouseTimer()
{
	//enough time has elapsed for the user to play around with the mouse movement
	//now we exit on mouse
	m_bTerminateOnMouse = TRUE;

	return TRUE;
}

BOOL CClockSaverWnd::OnQuickExitTimer()
{
	//the quick exit timer has elapsed
	//moving the mouse no longer exits the screen saver
	m_bTerminateOnMouse = FALSE;

	if ( m_ClockSettings.GetExitOnMouse() )
		SET_TIMER( TIMER_MOUSEEXIT, m_ClockSettings.GetMouseTimeOutInterval() * 60000 );

	return TRUE;
}

BOOL CClockSaverWnd::OnIdleTimer()
{
	m_pClock->SetPoint( GetRandomPoint() );

	return FALSE;
}

CPoint CClockSaverWnd::GetRandomPoint()
{
	//generate a random position on the screen such that
	//the entire clock is still on the screen

	// first pick a random monitor
	CMonitors monitors;
	CMonitor monitor = monitors.GetMonitor( RANDOM( 0, CMonitors::GetMonitorCount() - 1 ) );

	// get the rect of that monitor
	CRect rect;
	monitor.GetMonitorRect( &rect );
	ScreenToClient( &rect );

	// shrink the rect enough so that the whole clock will be on screen
	int deflate = (int)(m_pClock->GetDiameter() * 2.0);
	rect.DeflateRect( deflate, deflate );

	// then get a random point in that rect
	int x = RANDOM( rect.left, rect.right );
	int y = RANDOM( rect.top, rect.bottom );

	return CPoint( x, y );
}

void CClockSaverWnd::OnMouseMove(UINT nFlags, CPoint point)
{
	if( m_bTerminateOnMouse )
	{
		CSaverWnd::OnMouseMove( nFlags, point );	// SaverWnd closes the scren saver on mouse movement
		return;
	}

	if ( m_ClockSettings.GetShutDownComputer() )
		RESET_TIMER( TIMER_SHUTDOWN_COMPUTER, m_ClockSettings.GetShutDownComputerInterval() * 60 * 60 * 1000 );

	if ( m_ClockSettings.GetExitOnMouse() )
		RESET_TIMER( TIMER_MOUSEEXIT, m_ClockSettings.GetMouseTimeOutInterval() * 60000 );

	RESET_TIMER( TIMER_START_WIGGLE_MOUSE, RANDOM( 60000, 120000 ) );
	RESET_TIMER( TIMER_IDLE, RANDOM( 30000, 300000 ) );

	// bypass the saverwnd which closes the window on mouse movement
	CClockWnd::OnMouseMove( nFlags, point );
}

void CClockSaverWnd::OnGotReminder( CReminder* pReminder )
{
	m_RemindersArray.Add( pReminder );

	// make sure the new reminder gets painted
	CRect rect;
	CMonitors::GetPrimaryMonitor().GetMonitorRect( &rect );
	ScreenToClient( &rect );
	InvalidateRect( &rect );
}

void CClockSaverWnd::CloseSaverWindow()
{
	m_bReallyClose = TRUE;
	this->PostMessage( WM_CLOSE );
}

bool CClockSaverWnd::ShutDownComputer()
{
   HANDLE hToken; 
   TOKEN_PRIVILEGES tkp; 
 
   // Get a token for this process. 
   if (!::OpenProcessToken(::GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
      return false; 
 
   // Get the LUID for the shutdown privilege. 
   ::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); 
 
   tkp.PrivilegeCount = 1;  // one privilege to set    
   tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
 
   // Get the shutdown privilege for this process. 
   ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); 
 
   if (::GetLastError() != ERROR_SUCCESS) 
      return false; 
 
   // Shut down the system and power off 
   if (!::ExitWindowsEx(EWX_POWEROFF, SHTDN_REASON_FLAG_PLANNED)) 
      return false; 

   return true;
}

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
Team Leader Starkey Laboratories
United States United States
The first computer program I ever wrote was in BASIC on a TRS-80 Model I and it looked something like:
10 PRINT "Don is cool"
20 GOTO 10

It only went downhill from there.

Hey look, I've got a blog

Comments and Discussions