//////////////////////////////////////////////////////////////////////
// ColorSpy Copyright 2003 Tom Furuya [tom_furuya@yahoo.com]
//
// Readers of this article may copy the code for use in developing their own applications.
// If the code is used in a commercial application, however, an acknolegement must
// be included in the following form:
// "Segment of the code (c) 2003 Tom Furuya for CodeProject.com".
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_)
#define AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_
#pragma once
#include "ColorBox.h"
#include "ColorSpyCBViewer.h"
#include "SysColors.h"
#include "ColorHelper.h"
#include "Magnifier.h"
class CColorSpyDlg
: public CDialogImpl<CColorSpyDlg>
, public CUpdateUI<CColorSpyDlg>
, public CMessageFilter
, public CIdleHandler
, public CColorSpyCBViewer<CColorSpyDlg> // Clipboard viewer window
// , public CLayeredWindowT<CColorSpyDlg>
{
public:
CColorBox m_colorBox;
CContainedWindowT<CEdit> m_infoBox;
CPoint m_ptCursor;
bool m_bSticked;
COLORREF m_clrPixel;
// ToolTip
CToolTipCtrl m_ToolTip;
LPTSTR m_szToolTipText;
// Color format
CColorHelper::ColorFormat m_colorFormat;
// Magnifier
CMagnifierDlg m_Magnifier;
enum { IDD = IDD_MAINDLG };
enum { GAP = 3 };
CColorSpyDlg()
: m_bSticked(false)
, m_infoBox(_T("edit"), this, IDC_INFO)
, m_szToolTipText(NULL)
, m_colorFormat(CColorHelper::CF_WEB)
{ }
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_CHANGECBCHAIN)
{
ATLTRACE("WM_CHANGECBCHAIN receirved!\n");
}
// Tickle tooltip control with mouse messages
if (WM_MOUSEFIRST <= pMsg->message && pMsg->message <= WM_MOUSELAST)
RelayEvent(pMsg);
return IsDialogMessage(pMsg);
}
virtual BOOL OnIdle()
{
return FALSE;
}
BEGIN_UPDATE_UI_MAP(CColorSpyDlg)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CColorSpyDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnAppExit)
COMMAND_ID_HANDLER(ID_VIEW_MAGNIFIER, OnViewMagnifier)
//
// Color representation format
//
COMMAND_RANGE_HANDLER(ID_REP_HEX, ID_REP_HSB, OnColorRep)
//
// ColorSpy App message handler
//
MESSAGE_HANDLER(WM_APP_COLORSPY, OnColorSpy)
//
// Note: Since ColorSpy tells Windows that this window is caption
// in NCHITTEST message handler, WM_[LR]BUTTON***, WM_CONTEXTMENU
// will not be sent to this window, but WM_NC*** ones.
//
MESSAGE_HANDLER(WM_NCRBUTTONUP, OnRButtonUp)
MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnLButtonDown)
//
// "Sticky" rubber (to the top edge of screen)
//
MESSAGE_HANDLER(WM_MOVING, OnMoving)
//
// Turn this window class into a CB Vierer for reading color [code|name]
//
CHAIN_MSG_MAP_ALT(CColorSpyCBViewer<CColorSpyDlg>, 1)
// Reflection for Colorbox STATIC
REFLECT_NOTIFICATIONS()
// Edit control
ALT_MSG_MAP(IDC_INFO)
// Tickle ToolTip control on Infobox to display supplimental info hesitantly.
// ToolTips is only aware of mouse messages between WM_MOUSEFIRST and WM_MOUSELAST.
MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
MESSAGE_HANDLER(WM_SYSKEYDOWN, OnSysKeyDown)
END_MSG_MAP()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
// Message handlers
void UpdateLayout()
{
CRect rect; GetClientRect(&rect);
CRect rcCB; m_colorBox.GetClientRect(&rcCB);
// Infobox
CRect rcIB; rcIB.CopyRect(rcCB);
rcIB.left = rcCB.right + GAP;
int cxInfobox = (CColorHelper::CF_WEB == m_colorFormat) ? 120 : 150;
rcIB.right = rcIB.left + cxInfobox;
m_infoBox.SetWindowPos(NULL, &rcIB, SWP_NOZORDER);
// Dialog
SIZE size = { rcCB.Width() + GAP + rcIB.Width() + 2 * GAP,
rcCB.Height() + 2 * GAP };
rect.SetRectEmpty();
rect.right = size.cx; rect.bottom = size.cy;
ModifyStyle(DS_3DLOOK,0);
// Create region and assign to window
CRgn rgn;
rgn.CreateRectRgn(0, 0, rect.Width(), rect.Height());
SetWindowRgn(rgn, TRUE);
SetWindowPos(HWND_TOPMOST, &rect, SWP_NOMOVE);
}
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CProfile profile;
// Set widnow position
int xLeft = profile.GetInt(_T("General"), _T("x"), 0);
int yTop = profile.GetInt(_T("General"), _T("y"), 0);
if (xLeft == 0 && yTop == 0)
CenterWindow();
else
::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
// Set icons
HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
SetIcon(hIcon, TRUE);
HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
SetIcon(hIconSmall, FALSE);
// Register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
// ToolTip
m_ToolTip.Create(m_hWnd);
m_ToolTip.ModifyStyle(0, TTS_ALWAYSTIP);// | TTS_BALLOON);
// Colorbox
CRect rcBox; rcBox.SetRectEmpty();
rcBox.bottom = rcBox.right = 17;
if (!(HWND)m_colorBox)
m_colorBox.Create(m_hWnd, rcBox, NULL, 0, 0, IDC_COLORBOX);
m_colorBox.SetFocus();
// Infobox
m_infoBox.SubclassWindow(GetDlgItem(IDC_INFO));
m_infoBox.SetReadOnly();
UIAddChildWindowContainer(m_hWnd);
UpdateLayout();
bHandled = FALSE;
return TRUE;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
m_infoBox.UnsubclassWindow();
return 0;
}
LRESULT OnColorRep(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
switch (wID)
{
default:
case ID_REP_HEX: m_colorFormat = CColorHelper::CF_WEB; break;
case ID_REP_DEC: m_colorFormat = CColorHelper::CF_DEC; break;
case ID_REP_HSB: m_colorFormat = CColorHelper::CF_HSB; break;
}
if (m_colorBox.IsLocked())
ShowInfo();
UpdateLayout();
return 0;
}
void ShowInfo()
{
CColorHelper helper;
CString strColorRep = helper.ToString(m_colorFormat, m_clrPixel);
m_colorBox.SetColor(m_clrPixel);
CString str;
switch (m_colorFormat)
{
default:
case CColorHelper::CF_WEB:
str.Format(_T("%d, %d #%s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
break;
case CColorHelper::CF_DEC:
str.Format(_T("%d, %d %s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
break;
case CColorHelper::CF_HSB:
str.Format(_T("%d, %d %s"), m_ptCursor.x, m_ptCursor.y, strColorRep);
break;
}
((CEdit)m_infoBox).SetSel(0,-1);
((CEdit)m_infoBox).ReplaceSel(str);
}
LRESULT OnColorSpy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// retrieve pixel color
::GetCursorPos (&m_ptCursor);
HDC hdc = ::GetDC(HWND_DESKTOP); //entire screen
m_clrPixel = ::GetPixel(hdc, m_ptCursor.x, m_ptCursor.y);
::ReleaseDC(NULL, hdc); //release dc
// show info about the sampled color
ShowInfo();
// use CTRL+SHIFT keys for sampling
if (::GetAsyncKeyState(VK_CONTROL) < 0 && ::GetAsyncKeyState(VK_SHIFT) < 0)
{
// freeze color box
m_colorBox.Lock();
((CEdit)m_infoBox).SetReadOnly(FALSE);
// freeze magnifier also
m_Magnifier.Lock();
}
return 0;
}
//
// Making "sticky" ghost rectangle effect
//
// Spy tells WM_MOVING is the event to watch when you see ghost rubber rectangle.
// While mouse button is held down, other mouse event won't come.
//
LRESULT OnMoving(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
const int sticky_distance = 4;
RECT* rcGhost = (LPRECT)lParam;
CPoint pt; GetCursorPos(&pt);
pt -= m_ptCursor;
if (m_bSticked &&
pt.x * pt.x + pt.y * pt.y > sticky_distance * sticky_distance)
{
m_bSticked = false;
return 0;
}
// Stick to top edge of the screen
if (rcGhost->top <= sticky_distance)
{
CRect rcWindow; GetWindowRect(rcWindow);
rcWindow.OffsetRect((rcGhost->left - rcWindow.left), -rcWindow.top);
// Reposition ghost rubber
rcGhost->left = rcWindow.left;
rcGhost->top = rcWindow.top;
rcGhost->right = rcWindow.right;
rcGhost->bottom = rcWindow.bottom;
m_bSticked = true;
}
::GetCursorPos(&m_ptCursor);
return 0;
}
LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (WM_MOUSEMOVE == uMsg)
{
static CString strOld;
CColorHelper helper;
// install colorname for tooltip
CString strColorName = helper.GetColorName(m_clrPixel);
if (strOld != strColorName)
SetToolTipText(strColorName);
strOld = strColorName;
// bring the tooltip window above other popup windows
::SetWindowPos(m_ToolTip, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
}
bHandled = FALSE;
return 0;
}
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE; // Run hit test
// Check to see if mouse cursor hit colorBox
CPoint ptCursor(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
CRect rcColorBox; m_colorBox.GetWindowRect(&rcColorBox);
if (false == rcColorBox.PtInRect(ptCursor))
return 0;
CEdit edit = m_infoBox;
bool lock = !m_colorBox.IsLocked();
m_colorBox.Lock(lock);
// sync magnifier as well
m_Magnifier.Lock(lock);
if (lock)
{
// Move caret to the end for easier color code editing
int len = edit.GetWindowTextLength();
edit.SetSel(len, len);
edit.SetFocus();
edit.SetReadOnly(FALSE);
}
else
{
m_colorBox.SetFocus();
edit.SetReadOnly();
}
return 0;
}
LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return HTCAPTION; //treated as a CAPTION; WM_CONTEXTMENU will not come.
}
//
// Note: If right mouse button is clicked, for example,
// you receive message as WM_NCRBUTTON**, instead of WM_RBUTTON**
// as we say CAPTION.
LRESULT OnRButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CMenu menu; menu.LoadMenu(IDR_CONTEXT_MENU);
CMenuHandle hPopup = menu.GetSubMenu(0);
hPopup.SetMenuDefaultItem(ID_APP_EXIT);
if ((HWND)m_Magnifier)
{
hPopup.EnableMenuItem(ID_VIEW_MAGNIFIER, MF_GRAYED | MF_BYCOMMAND);
}
CPoint pt; GetCursorPos(&pt);
hPopup.TrackPopupMenu(0, pt.x, pt.y, m_hWnd); //takes pt in the screen coordinates
return 0;
}
//
// Make ColorSpy a well-behaved window app.
//
LRESULT OnSysKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
if (GetKeyState(VK_F4) < 0)
CloseDialog(0);
return 0;
}
LRESULT OnAppExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CRect rc; GetWindowRect(&rc);
CProfile iniFile;
iniFile.WriteInt(_T("General"), _T("x"), rc.left);
iniFile.WriteInt(_T("General"), _T("y"), rc.top);
CloseDialog(0);
return 0;
}
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
ShowWindow(SW_MINIMIZE);
ShowWindow(SW_HIDE);
CAboutDlg dlg;
dlg.DoModal();
ShowWindow(SW_RESTORE);
return 0;
}
LRESULT OnViewMagnifier(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
m_Magnifier.Create(m_hWnd, rcDefault);
m_Magnifier.ModifyStyle(0, WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME);
CRect rc; GetWindowRect(rc);
rc.OffsetRect(0, rc.Height());
m_Magnifier.MoveWindow(rc);
m_Magnifier.ShowWindow(SW_SHOW);
m_Magnifier.Lock(m_colorBox.IsLocked());
return 0;
}
void CloseDialog(int nVal)
{
// FadeOut(0xFF);
DestroyWindow();
::PostQuitMessage(nVal);
}
void SetColor(CString& text)
{
// Do nothing if Colorbox is active (Color sampling mode)
if (false == m_colorBox.IsLocked())
return;
// Color name/code scan mode
DuoT<bool, COLORREF> result = Validate(text);
if (result.v1())
SetColor(result.v2());
}
void SetColor(COLORREF clr)
{
m_clrPixel = clr;
CColorHelper helper;
m_colorBox.SetColor(m_clrPixel);
CString strText; strText.Format(_T("#%s"), helper.ToString(m_clrPixel));
m_infoBox.SetWindowText(strText);
}
DuoT<bool, COLORREF> Validate(const CString& strText)
{
const CString strFmt = _T("#RRGGBB");
const DuoT<bool, COLORREF> InvalidColor = make_duo(false, 0);
CColorHelper helper;
DuoT<bool, COLORREF> result;
if (strText.Left(1) != _T('#'))
{
result = helper.GetColorByName(strText);
if (result.v1())
return result;
result = CSysColors::GetColorByName(strText);
return result;
}
if (strText.GetLength() != strFmt.GetLength())
return InvalidColor;
result = helper.GetColor(strText.Right(6));
return result;
}
void RelayEvent(LPMSG lpMsg)
{
// Tickle ToolTip control
if (m_ToolTip.IsWindow())
m_ToolTip.RelayEvent(lpMsg);
}
bool SetToolTipText(LPCTSTR lpszText)
{
if (m_szToolTipText != NULL)
{
delete [] m_szToolTipText;
m_szToolTipText = NULL;
}
if (lpszText == NULL || lstrlen(lpszText) == 0)
{
if(m_ToolTip.IsWindow())
m_ToolTip.Activate(FALSE);
return true;
}
ATLTRY (m_szToolTipText = new TCHAR[lstrlen(lpszText) + 1]);
if (m_szToolTipText == NULL)
return false;
bool bRet = (lstrcpy(m_szToolTipText, lpszText) != NULL);
if(bRet && m_ToolTip.IsWindow())
{
m_ToolTip.Activate(TRUE);
m_ToolTip.AddTool(m_infoBox, m_szToolTipText);
}
return bRet;
}
//
// Simple INI Profile helper
//
class CProfile
{
public:
CProfile(LPCTSTR szIniFile = NULL)
{
m_strIniFilePath = GetModuleIniFilePath(szIniFile);
}
BOOL WriteInt(
LPCTSTR szSection,
LPCTSTR szKey,
int nVal
)
{
return _WritePrivateProfileInt(szSection, szKey, nVal, m_strIniFilePath);
}
int GetInt(
LPCTSTR szSection,
LPCTSTR szKey,
int nDefault
)
{
return _GetPrivateProfileInt(szSection, szKey, nDefault, m_strIniFilePath);
}
static CString GetModuleIniFilePath(LPCTSTR szIniFile = NULL)
{
TCHAR szModule1[_MAX_PATH] = { 0 };
TCHAR szModule2[_MAX_PATH] = { 0 };
TCHAR* pszFileName;
::GetModuleFileName(_Module.GetModuleInstance(), szModule1, _MAX_PATH);
::GetFullPathName(szModule1, _MAX_PATH, szModule2, &pszFileName);
CString strIniFileName = szModule2;
if (szIniFile)
{
int idx = strIniFileName.ReverseFind(_T('\\'));
strIniFileName.Format("%s\\%s", strIniFileName.Left(idx), szIniFile);
}
else
{
int idx = strIniFileName.ReverseFind(_T('.'));
strIniFileName.Format(_T("%s.ini"), strIniFileName.Left(idx));
}
return strIniFileName;
}
private:
CString m_strIniFilePath;
BOOL _WritePrivateProfileInt(
LPCTSTR szSection,
LPCTSTR szKey,
int nVal,
LPCTSTR szFile
)
{
TCHAR buffer[1024] = { 0 };
itoa(nVal, buffer, 10);
return ::WritePrivateProfileString(szSection, szKey, buffer, szFile);
}
int _GetPrivateProfileInt(
LPCTSTR szSection,
LPCTSTR szKey,
int nDefault,
LPCTSTR szFile
)
{
return ::GetPrivateProfileInt(szSection, szKey, nDefault, szFile);
}
};
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_MAINDLG_H__64E46429_CDA4_42FB_962A_5894FDB38FC1__INCLUDED_)