// Compass.cpp : implementation file
//
#include "stdafx.h"
#include "resource.h"
#include "DoublePoint.h"
#include "Polygon.h"
#include "Compass.h"
#include "SaveDC.h"
#include "DtoR.h"
#include "msg.h"
#include "RegVars.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
DECLARE_MESSAGE(CPM_SET_ANGLE)
/////////////////////////////////////////////////////////////////////////////
// CCompass
CCompass::CCompass()
{
// Note: for optimal performance, sort monotonically by font size
// Note: The first entry must be the largest
// The largest size is 100.0, which is the base size used
// for computing the offset circle
display.Add(new displayinfo(0.0, _T("N"), 100.0, TRUE));
display.Add(new displayinfo( 90.0, _T("E"), 90.0, FALSE));
display.Add(new displayinfo(180.0, _T("S"), 90.0, FALSE));
display.Add(new displayinfo(270.0, _T("W"), 90.0, FALSE));
display.Add(new displayinfo( 45.0, _T("NE"), 80.0, FALSE));
display.Add(new displayinfo(135.0, _T("SE"), 80.0, FALSE));
display.Add(new displayinfo(225.0, _T("SW"), 80.0, FALSE));
display.Add(new displayinfo(315.0, _T("NW"), 80.0, FALSE));
if(!arrow.Load(_T("IDP_ARROW")))
{ /* try disk file */
RegistryString compass(IDS_COMPASS);
compass.load();
if(compass.value.GetLength() == 0 || !arrow.Read(compass.value))
arrow.Read(_T("Arrow.pln")); // use default
} /* try disk file */
angle = 0.0; // initialize at North
ArrowVisible = FALSE;
}
CCompass::~CCompass()
{
}
/****************************************************************************
* CCompass::RegisterMe
* Result: BOOL
* TRUE if successful
* FALSE if error
* Effect:
* Registers the class. Note that this is called as a consequence of
* initializing a static variable (sneaky C++)
****************************************************************************/
BOOL CCompass::hasclass = CCompass::RegisterMe();
BOOL CCompass::RegisterMe()
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = ::DefWindowProc; // must be this value
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = (HINSTANCE)GetModuleHandle(NULL);
wc.hIcon = NULL; // child window has no icon
wc.hCursor = NULL; // we use OnSetCursor
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszMenuName = NULL; // no menu
wc.lpszClassName = COMPASS_CLASS_NAME;
return AfxRegisterClass(&wc);
} // CCompass::RegisterMe
BEGIN_MESSAGE_MAP(CCompass, CWnd)
//{{AFX_MSG_MAP(CCompass)
ON_WM_ERASEBKGND()
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
ON_WM_ENABLE()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CCompass message handlers
/****************************************************************************
* CCompass::CreateRegion
* Inputs:
* CRgn & rgn: Region to be initialized
* Result: CRect
* Bounding box of the region
* Effect:
* Creates a circular region within the control
****************************************************************************/
CRect CCompass::CreateClipRegion(CRgn & rgn)
{
CRect r;
GetClientRect(&r);
int radius = min(r.Width() / 2, r.Height() / 2);
CPoint center(r.Width() / 2, r.Height() / 2);
rgn.CreateEllipticRgn(center.x - radius, center.y - radius,
center.x + radius, center.y + radius);
return CRect(center.x - radius, center.y - radius,
center.x + radius, center.y + radius);
} // CCompass::CreateClipRegion
/****************************************************************************
* CCompass::OnEraseBkgnd
* Inputs:
* CDC * pDC: DC
* Result: BOOL
* TRUE if erased
* FALSE if not erased
* Effect:
* Erases the background outside the clipping region
****************************************************************************/
BOOL CCompass::OnEraseBkgnd(CDC* pDC)
{
CRgn rgn;
CSaveDC sdc(pDC);
CreateClipRegion(rgn);
pDC->SelectClipRgn(&rgn, RGN_DIFF); // remove circle from update area
return CWnd::OnEraseBkgnd(pDC);
}
/****************************************************************************
* CCompass::MapDC
* Inputs:
* CDC & dc: DC
* Result: void
*
* Effect:
* Maps the DC to the isotropic coordinates used for the compass
****************************************************************************/
void CCompass::MapDC(CDC & dc)
{
dc.SetMapMode(MM_ISOTROPIC);
CRect r;
GetClientRect(&r);
dc.SetWindowExt(r.Width(), r.Height());
dc.SetViewportExt(r.Width(), -r.Height());
CPoint center(r.left + r.Width() / 2, r.top + r.Height() / 2);
dc.SetViewportOrg(center.x, center.y);
} // CCompass::MapDC
/****************************************************************************
* CCompass::OnPaint
* Result: void
*
* Effect:
* Paints the compass
****************************************************************************/
void CCompass::OnPaint()
{
CPaintDC dc(this); // device context for painting
CBrush br(::GetSysColor(COLOR_INFOBK));
CRgn rgn;
CRect r;
r = CreateClipRegion(rgn);
#define BORDER_WIDTH 2
CPen border(PS_SOLID, BORDER_WIDTH, RGB(0,0,0));
CBrush needle(RGB(255, 0, 0));
#define ENABLED_COLOR RGB(0,0,0)
#define DISABLED_COLOR RGB(128,128,128)
CPen enabledPen(PS_SOLID, 0, ENABLED_COLOR);
CPen disabledPen(PS_SOLID, 0, DISABLED_COLOR);
//----------------------------------------------------------------
// GDI resources must be declared above this line
//----------------------------------------------------------------
CSaveDC sdc(dc);
dc.SelectClipRgn(&rgn); // clip to compass
dc.FillRgn(&rgn, &br);
// Convert the origin to the center of the circle
CPoint center(r.left + r.Width() / 2, r.top + r.Height() / 2);
// Renormalize the rectangle to the center of the circle
r -= center;
int radius = r.Width() / 2;
dc.SetBkMode(TRANSPARENT);
MapDC(dc);
// Draw the borders
{
CSaveDC sdc2(dc);
dc.SelectClipRgn(NULL);
dc.SelectStockObject(HOLLOW_BRUSH);
dc.SelectObject(&border);
dc.Ellipse(-radius, -radius, radius, radius);
r.InflateRect(-BORDER_WIDTH, -BORDER_WIDTH);
radius = r.Width() / 2;
}
radius = r.Width() / 2;
dc.SelectObject(IsWindowEnabled() ? &enabledPen : &disabledPen);
// Draw N-S line
dc.MoveTo(0, radius);
dc.LineTo(0, -radius);
// Draw E-W line
dc.MoveTo(-radius, 0);
dc.LineTo(radius, 0);
// Draw SW-NE line
dc.MoveTo((int)(radius * sin(DegreesToRadians(GeographicToGeometric(225.0)))),
(int)(radius * cos(DegreesToRadians(GeographicToGeometric(225.0)))) );
dc.LineTo((int)(radius * sin(DegreesToRadians(GeographicToGeometric( 45.0)))),
(int)(radius * cos(DegreesToRadians(GeographicToGeometric( 45.0)))) );
// Draw NW-SE line
dc.MoveTo((int)(radius * sin(DegreesToRadians(GeographicToGeometric(315.0)))),
(int)(radius * cos(DegreesToRadians(GeographicToGeometric(315.0)))) );
dc.LineTo((int)(radius * sin(DegreesToRadians(GeographicToGeometric(135.0)))),
(int)(radius * cos(DegreesToRadians(GeographicToGeometric(135.0)))) );
// Now create the font elements
// The symbols are placed along a circle which is inscribed
// within the compass area
//
// +-----------------------------+
// / N \
// / NW | NE \
// / | \
// | | |
// | W-----+-----E |
// | | |
// \ | /
// \ SW | SE /
// \ S /
// +-----------------------------+
double size = 0.15 * (double)r.Width();
double CurrentFontSize = 0.0; // current font size
CFont * f = NULL;
dc.SetTextColor(IsWindowEnabled() ? ENABLED_COLOR : DISABLED_COLOR);
innerRadius = radius;
for(int i = 0; i < display.GetSize(); i++)
{ /* draw points */
CSaveDC sdc2(dc);
dc.SetBkMode(OPAQUE);
dc.SetBkColor(::GetSysColor(COLOR_INFOBK));
if(display[i]->GetSize() != CurrentFontSize)
{ /* new font */
if(f != NULL)
delete f;
f = display[i]->CreateFont(size, _T("Times New Roman"));
} /* new font */
dc.SelectObject(f);
CurrentFontSize = display[i]->GetSize();
CString text = display[i]->GetText();
//
// 4 | 1
// --+--
// 3 | 2
//------------------------------------------------------------------
// � qdant x y x-origin y-origin alignment
// ----------------------------------------------------------------
// 0 4.1 0 >0 x-w/2 y TOP, LEFT
// <90 1 >0 >0 x y TOP, RIGHT
// 90 1.2 >0 0 x y-h/2 TOP, RIGHT
// <180 2 >0 <0 x y BOTTOM, RIGHT
// 180 2.3 0 <0 x-w/2 y BOTTOM, RIGHT
// <270 3 <0 <0 x y BOTTOM, LEFT
// 270 3.4 <0 0 x y-h/2 TOP, LEFT
// <360 4 <0 >0 x y TOP, LEFT
//text.Format(_T("%.0f"), display[i]->GetAngle());
int x = (int)(radius * cos(DegreesToRadians(GeographicToGeometric(display[i]->GetAngle()))));
int y = (int)(radius * sin(DegreesToRadians(GeographicToGeometric(display[i]->GetAngle()))));
//text.Format(_T("%d,%d"), x, y);
CSize textSize = dc.GetTextExtent(text);
double theta = display[i]->GetAngle();
if(theta == 0.0)
{ /* 0 */
dc.SetTextAlign(TA_TOP | TA_LEFT);
x -= textSize.cx / 2;
innerRadius = radius - textSize.cy;
} /* 0 */
else
if(theta < 90.0)
{ /* < 90 */
dc.SetTextAlign(TA_TOP | TA_RIGHT);
} /* < 90 */
else
if(theta == 90.0)
{ /* 90 */
dc.SetTextAlign(TA_TOP | TA_RIGHT);
y += textSize.cy / 2;
} /* 90 */
else
if(theta < 180.0)
{ /* < 180 */
dc.SetTextAlign(TA_BOTTOM | TA_RIGHT);
} /* < 180 */
else
if(theta == 180.0)
{ /* 180 */
dc.SetTextAlign(TA_BOTTOM | TA_LEFT);
x -= textSize.cx / 2;
} /* 180 */
else
if(theta < 270.0)
{ /* < 270 */
dc.SetTextAlign(TA_BOTTOM | TA_LEFT);
} /* < 270 */
else
if(theta == 270)
{ /* 270 */
dc.SetTextAlign(TA_TOP | TA_LEFT);
y += textSize.cy / 2;
} /* 270 */
else
{ /* < 360 */
dc.SetTextAlign(TA_TOP | TA_LEFT);
} /* < 360 */
dc.TextOut(x, y, text);
} /* draw points */
if(f != NULL)
delete f;
// Draw the arrow
if(IsWindowEnabled() && ArrowVisible)
{ /* draw arrow */
CRect bb = arrow.GetInputBB();
dc.SelectObject(&needle);
arrow.Transform(angle, (double)abs(bb.Height()) / (2.0 * (double)radius));
arrow.Draw(dc, CDoublePoint(0.0, 0.0));
} /* draw arrow */
// Do not call CWnd::OnPaint() for painting messages
}
/****************************************************************************
* CCompass::SetAngle
* Inputs:
* double direction: Desired angle
* Result: void
*
* Effect:
* Sets the angle of the arrow to the specified angle
****************************************************************************/
void CCompass::SetAngle(double direction)
{
if(angle == direction) // works only because angles are integers converted to floats
return;
CRect r;
GetClientRect(&r);
int radius = r.Width() / 2;
angle = direction;
// The reason for doing all this work, instead of a simple
// InvalidateRect(NULL), is that this reduces flicker
CClientDC dc(this);
MapDC(dc);
CRgn inner;
CRect rr(-innerRadius, -innerRadius,
innerRadius, innerRadius);
dc.LPtoDP(&rr);
rr.NormalizeRect();
inner.CreateEllipticRgn(rr.left, rr.top, rr.right, rr.bottom);
InvalidateRgn(&inner);
} // CCompass::SetAngle
/****************************************************************************
* CCompass::SetShow
* Inputs:
* BOOL mode: TRUE to show, FALSE to hide
* Result: void
*
* Effect:
* Shows/hides the arrow
****************************************************************************/
void CCompass::SetShow(BOOL mode)
{
if(mode == ArrowVisible)
return;
ArrowVisible = mode;
//PTR<CRgn> rgn (arrow.GetRgn());
//InvalidateRgn(rgn);
InvalidateRect(NULL);
} // CCompass::SetShow
/****************************************************************************
* CCompass::NotifyParent
* Inputs:
* CPoint point: Point of mouse
* Result: void
*
* Effect:
* Notifies the parent of the mouse position
****************************************************************************/
void CCompass::NotifyParent(CPoint point)
{
CClientDC dc(this);
MapDC(dc);
dc.DPtoLP(&point);
GetParent()->SendMessage(CPM_SET_ANGLE, (WPARAM)point.x, (LPARAM)point.y);
} // CCompass::NotifyParent
/****************************************************************************
* CCompass::OnLButtonDown
* Inputs:
* UINT nFlags:
* CPoint point: Point in client coordinates
* Result: void
*
* Effect:
* Notifies the parent of the x,y in mapped coordinates
****************************************************************************/
void CCompass::OnLButtonDown(UINT nFlags, CPoint point)
{
CRgn rgn;
CreateClipRegion(rgn);
if(rgn.PtInRegion(point))
{ /* in region */
SetCapture();
NotifyParent(point);
return;
} /* in region */
CWnd::OnLButtonDown(nFlags, point);
}
/****************************************************************************
* CCompass::OnEnable
* Inputs:
* BOOL bEnable: TRUE if enabling, FALSE if disabling
* Result: void
*
* Effect:
* Forces update so window will be redrawn in the correct mode
****************************************************************************/
void CCompass::OnEnable(BOOL bEnable)
{
CWnd::OnEnable(bEnable);
InvalidateRect(NULL);
}
/****************************************************************************
* CCompass::OnMouseMove
* Inputs:
* UINT nFlags: flags, ignored unless passed to superclass
* CPoint point: Point at which mouse is clicked
* Result: void
*
* Effect:
* Notifies the parent the mouse has moved if there is capture
****************************************************************************/
void CCompass::OnMouseMove(UINT nFlags, CPoint point)
{
if(GetCapture() != NULL)
{ /* notify parent */
NotifyParent(point);
return;
} /* notify parent */
CWnd::OnMouseMove(nFlags, point);
}
/****************************************************************************
* CCompass::OnLButtonUp
* Inputs:
* UINT nFlags: ignored except when passed to superclass
* CPoint point: Mouse position
* Result: void
*
* Effect:
* Notifies the parent of the change and release capture
****************************************************************************/
void CCompass::OnLButtonUp(UINT nFlags, CPoint point)
{
if(GetCapture() != NULL)
{ /* notify */
NotifyParent(point);
ReleaseCapture();
} /* notify */
CWnd::OnLButtonUp(nFlags, point);
}