ToolTips in a View and TTN_NEEDTEXT

From: Rick Lee (Rick_at_Custom-Made-SoftwareNOSPAM.com)
Date: 11/19/04


Date: Thu, 18 Nov 2004 23:42:25 -0800

I have been trying to reuse code for ToolTips in a View ("Data Tips")
that I found in the
April 97 issue of MSJ:
   http://www.microsoft.com/msj/0497/tooltip/tooltip.aspx

Most of the required functions and portions of functions --
OnMouseMove(), OnInitialUpdate(),
PreTranslateMessage() HitTest() -- are called correctly. But for some
reason the TTN_NEEDTEXT
message appears never to be sent because OnToolTipNeedText() is never
called.

Note that the only value of pMsg->message that is relayed to the ToolTip
is 512, and I have
no idea if that's right. But it does seem to be the case in the Sample
Project I downloaded.
(Click tooltips.exe at the top of the article for the self-extracting
archive. The Data Tips
is in the DTTest directory.)

Thanks,
Rick Lee

I know it's a mess, but here is my code if you want to look:

////////////////////////////////////////////
//
// DayView.cpp
//
////////////////////////////////////////////

// DayView.cpp : implementation file
//

#include "stdafx.h"
#include "clockwatcher.h"
#include "ClockWatcherDoc.h"
#include "QueryWorkDaysSet.h"
#include "PayPeriodView.h"
#include "DayView.h"
#include "WorkDayParams.h"
#include "SortableArray.h"
#include "ProjectColor.h"

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

 extern CWorkDayParams g_workDayParams;
 extern CArrayEx <CSingleProject, CSingleProject> g_projectList;
 extern CProjectColorList g_projectColorList;
 extern CArray<CQWorkPeriodRecord, CQWorkPeriodRecord> g_workPeriodSet;
 extern int g_minWorkPeriod;
  CArray<CQWorkPeriodRecord, CQWorkPeriodRecord> g_workPeriodSet;

 static CString dayNames[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
"Sat"
 };
 static int numberOfDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
30, 31};

/////////////////////////////////////////////////////////////////////////////

// CDayView

IMPLEMENT_DYNCREATE(CDayView, CScrollView)

CDayView::CDayView()
{
 xOrigin = 50;
 rightMargin = xOrigin;
 yOrigin = 2;
 bottomMargin = yOrigin;
 dayHeight = 68;
 hourWidth = 80;
 hourHeight = 60;
 taskWidth = 400;
 totalDayWidth = hourWidth + taskWidth;

 legendLeft = xOrigin + totalDayWidth + rightMargin;
 legendWidth = 250;
 legendBoxWidth = 40;
 legendBoxHeight = 20;

 // Set button parameters
 centerBtnHeight = 23;
 arrowBtnHeight = 41;
  yButton = yOrigin + (dayHeight-arrowBtnHeight)/2;
 arrowWidth = 66;
 centerWidth = 140;
 groupWidth = 2*arrowWidth + centerWidth + 6;
 xBorder = (totalDayWidth-groupWidth) / 2;

 m_pRectHit = NULL;
}

CDayView::~CDayView()
{
 delete m_dayArrowLeft;
 delete m_daySelect;
 delete m_dayArrowRight;
 m_brushBlackHatched.DeleteObject();
 m_brushYellowSolid.DeleteObject();
 for (int i=0; i<CMS_BRIGHT_COLORS; i++) {
  m_fillBrush[i].DeleteObject();
 }

 m_penBlackSolid.DeleteObject();
 m_penBlackThin.DeleteObject();
 m_penWhiteDotted.DeleteObject();
 m_penWhiteSolid.DeleteObject();

 m_fontArialTitle.DeleteObject();
 m_fontArialBody.DeleteObject();
}

BEGIN_MESSAGE_MAP(CDayView, CScrollView)
 ON_BN_CLICKED(IDC_CALENDAR_MONTH, OnClickDaySelect)
 ON_BN_CLICKED(IDC_CALENDAR_LEFT, OnClickLeftArrow)
 ON_BN_DOUBLECLICKED(IDC_CALENDAR_LEFT, OnDblClickLeftArrow)
 ON_BN_CLICKED(IDC_CALENDAR_RIGHT, OnClickRightArrow)
 ON_BN_DOUBLECLICKED(IDC_CALENDAR_RIGHT, OnDblClickRightArrow)
 ON_COMMAND(ID_DAYLOG_SUN, OnDayLog_Sun)
 ON_COMMAND(ID_DAYLOG_MON, OnDayLog_Mon)
 ON_COMMAND(ID_DAYLOG_TUE, OnDayLog_Tue)
 ON_COMMAND(ID_DAYLOG_WED, OnDayLog_Wed)
 ON_COMMAND(ID_DAYLOG_THU, OnDayLog_Thu)
 ON_COMMAND(ID_DAYLOG_FRI, OnDayLog_Fri)
 ON_COMMAND(ID_DAYLOG_SAT, OnDayLog_Sat)
 ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipNeedText)
 //{{AFX_MSG_MAP(CDayView)
 ON_WM_MOUSEMOVE()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CDayView::CreateAllFonts()
{
 LOGFONT fontSpec;
 CString message = "Resources are critically low.\n";
 message += "Close other programs in order to continue.";

 // Create Title font.
 memset(&fontSpec, 0, sizeof(LOGFONT)); // clear out structure.
 strcpy(fontSpec.lfFaceName, "Arial");
 fontSpec.lfHeight = 120;
 fontSpec.lfWeight = FW_BOLD;
 if (!m_fontArialTitle.CreatePointFontIndirect(&fontSpec, NULL)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }

 // Create Body font.
 memset(&fontSpec, 0, sizeof(LOGFONT)); // clear out structure.
 strcpy(fontSpec.lfFaceName, "Arial");
 fontSpec.lfHeight = 90;
 fontSpec.lfWeight = FW_BOLD;
 if (!m_fontArialBody.CreatePointFontIndirect(&fontSpec, NULL)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }
}

void CDayView::CreateAllPens()
{
 CString message = "Resources are critically low.\n";
 message += "Close other programs in order to continue.";

 if (!m_penWhiteSolid.CreatePen(PS_SOLID, 3, CMS_WHITE)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }

 if (!m_penWhiteDotted.CreatePen(PS_DOT, 0, CMS_WHITE)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }

 if (!m_penBlackSolid.CreatePen(PS_SOLID, 3, CMS_BLACK)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }

 if (!m_penBlackThin.CreatePen(PS_SOLID, 1, CMS_BLACK)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }
}

void CDayView::CreateAllBrushes()
{
 CString message = "Resources are critically low.\n";
 message += "Close other programs in order to continue.";

 for (int i=0; i<CMS_BRIGHT_COLORS; i++) {
  COLORREF color = g_projectColorList.GetColor(i);
  if (!m_fillBrush[i].CreateSolidBrush(color)) {
   // Alert the user that resources are low
   AfxMessageBox(message);
   return;
  }
 }

 if (!m_brushYellowSolid.CreateSolidBrush(CMS_YELLOW)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }

 if (!m_brushBlackHatched.CreateHatchBrush(HS_FDIAGONAL, CMS_BLACK)) {
  // Alert the user that resources are low
  AfxMessageBox(message);
  return;
 }
}

/////////////////////////////////////////////////////////////////////////////

// CDayView drawing

void CDayView::OnDraw(CDC* pDC)
{
 CDocument* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 // TODO: add draw code here
 int oldMapMode = pDC->GetMapMode();
 pDC->SetMapMode(MM_TEXT);

 // Draw the day's worklog
 int numHours = this->CalcNumHours();
 totalHeight = dayHeight + 2 +
     CMS_NINT(numHours*hourHeight);
 this->DrawDayLog(pDC);
 pDC->SetMapMode(oldMapMode);
}

void CDayView::DrawDayLog(CDC* pDC)
{
 // Top-level drawing routine.
 // Add major steps here.

 this->DrawDayLogBackground(pDC);
 this->DrawDayLogBorders(pDC);
 this->WriteDateHeader(pDC);
 COLORREF oldBkColor = pDC->SetBkColor(CMS_BKGND_GRAY);
 this->DrawDayLogHours(pDC);
 this->PlaceHourArrows(pDC);
 this->DrawWorkPeriods(pDC);
 pDC->SetBkColor(oldBkColor);
}

void CDayView::DrawDayLogBackground(CDC* pDC)
{
 int x = xOrigin; int y = yOrigin;
 int xExtent = totalDayWidth + 2;
 int yExtent = totalHeight;
 pDC->FillSolidRect(x, y, xExtent, yExtent, CMS_BKGND_GRAY);
 pDC->SetBkColor(CMS_WHITE);
}

void CDayView::DrawDayLogBorders(CDC* pDC)
{
 // Select the solid black pen into the device context
    // Save the old pen at the same time
    CPen* pOldPen = pDC->SelectObject(&m_penBlackSolid);

    // Draw Day header box of Log.
    pDC->MoveTo(xOrigin, yOrigin);
    pDC->LineTo(xOrigin+totalDayWidth, yOrigin);
    pDC->LineTo(xOrigin+totalDayWidth, yOrigin+dayHeight);
    pDC->LineTo(xOrigin, yOrigin+dayHeight);
    pDC->LineTo(xOrigin, yOrigin);

 // Draw border around rest of display.
    pDC->MoveTo(xOrigin+totalDayWidth, dayHeight);
    pDC->LineTo(xOrigin+totalDayWidth, totalHeight);
    pDC->LineTo(xOrigin+hourWidth, totalHeight);

 // Draw one column of black hour boxes.
 int x = xOrigin; int y = yOrigin+dayHeight;
 pDC->FillSolidRect(x-1, y, hourWidth+3, totalHeight-dayHeight,
CMS_BLACK);
 pDC->SetBkColor(CMS_WHITE);

 // Draw solid white lines to divide whole hours.
    pDC->SelectObject(&m_penWhiteSolid);
 int numHours = this->CalcNumHours();
 for (int i=1; i<numHours; i++) {
  pDC->MoveTo(xOrigin, yOrigin+dayHeight+i*hourHeight);
  pDC->LineTo(xOrigin+hourWidth, yOrigin+dayHeight+i*hourHeight);
 }

 // Draw dotted white lines to divide half hours.
 int oldBkMode = pDC->SetBkColor(TRANSPARENT);
    pDC->SelectObject(&m_penWhiteDotted);
 for (int j=1; j<=numHours; j++) {
  pDC->MoveTo(xOrigin,

yOrigin+2+CMS_NINT(0.5*dayHeight)+j*hourHeight);
  pDC->LineTo(xOrigin+hourWidth+1,

yOrigin+2+CMS_NINT(0.5*dayHeight)+j*hourHeight);
 }
 pDC->SetBkMode(oldBkMode);

    // Restore the old pen to the device context
    pDC->SelectObject(pOldPen);
}

void CDayView::WriteDateHeader(CDC* pDC)
{
 char term = '\0';
 char ss[32];
 COleDateTime today(currentYear, currentMonth, currentDay, 0, 0, 0);
 CString out = dayNames[today.GetDayOfWeek()-1];
 out += ", ";
 sprintf(ss, "%d/%d/%d%c", currentMonth, currentDay, currentYear, term);

 out += ss;

 CPoint pt(xOrigin+xBorder+arrowWidth+1, yButton+centerBtnHeight+3);
 CSize sz(centerWidth, arrowBtnHeight-centerBtnHeight-3);
 CRect rc(pt,sz);
 pDC->DrawText(out, rc, DT_CENTER|DT_VCENTER);
}

void CDayView::DrawDayLogHours(CDC* pDC)
{
 COLORREF oldBkColor = pDC->SetBkColor(CMS_BLACK);
 COLORREF oldTextColor = pDC->SetTextColor(CMS_WHITE);

 // We may need to display a start time
 // earlier than the actual time.
 int iStartTime = this->AdjustedStartTime();

 // For text drawing, the size of the rectangle is constant.
 int halfHour = CMS_NINT(0.5*(float)hourHeight) - 3;
 CSize sz(hourWidth, halfHour);

 int numHours = this->CalcNumHours();
 char hrStr[6];
 char term = '\0';

 // Draw full hours
 for (int i=0; i<numHours; i++) {
  sprintf(hrStr, "%d:00%c", iStartTime+i, term);
  CPoint pt(xOrigin, yOrigin+dayHeight+i*hourHeight+8);
  CRect rc(pt,sz);
  pDC->DrawText(hrStr, rc, DT_CENTER|DT_VCENTER);
 }

 // Draw half hours.
 for (int j=0; j<numHours; j++) {
  sprintf(hrStr, "%d:30%c", iStartTime+j, term);
  CPoint pt(xOrigin, yOrigin+dayHeight+j*hourHeight+halfHour+8);
  CRect rc(pt,sz);
  pDC->DrawText(hrStr, rc, DT_CENTER|DT_VCENTER);
 }

 pDC->SetBkColor(oldBkColor);
 pDC->SetTextColor(oldTextColor);
}

int CDayView::CalcNumHours()
{
 // We may need to display a start time
 // earlier than the actual time.
 int iStartTime = this->AdjustedStartTime();

 // We may need to display a stop time
 // later than the actual time.
 int iStopTime = this->AdjustedStopTime();

 return(iStopTime - iStartTime);
}

int CDayView::AdjustedStartTime()
{
 // We may need to display a start time
 // earlier than the actual time.
 double dStartTime = g_workDayParams.GetStartTimeDouble();
 int iStartTime = CMS_NINT(dStartTime);
 if (dStartTime < iStartTime) {
  iStartTime--;
 }
 return iStartTime;
}

int CDayView::AdjustedStopTime()
{
 // We may need to display a stop time
 // later than the actual time.
 double dStopTime = g_workDayParams.GetStopTimeDouble();
 int iStopTime = CMS_NINT(dStopTime);
 if (dStopTime > iStopTime) {
  iStopTime++;
 }
 return iStopTime;
}

void CDayView::PlaceHourArrows(CDC* pDC)
{
 // The x-coord of the right vertex is the same for both triangles.
 int x = xOrigin;
 int y;

 // Calculate the position of the right-hand vertex
 // of the START Time.
 double dStartTime = g_workDayParams.GetStartTimeDouble();
 this->TimeToY(dStartTime, &y);
 CPoint pt(x,y);
 this->DrawHourTriangle(pDC, pt);

 // Calculate the position of the right-hand vertex
 // of the STOP Time.
 double dStopTime = g_workDayParams.GetStopTimeDouble();
 this->TimeToY(dStopTime, &y);
 pt.y = y;
 this->DrawHourTriangle(pDC, pt);
}

BOOL CDayView::TimeToY(double time, int* pY)
{
 int yBase = yOrigin + dayHeight;

 // Subtract 2 times
 int timeHr = (int)time;
 int timeMin = (int)((time - (double)timeHr) * 100.0);
 double fractionMin = (double)timeMin / 60.0;
 double fractionTime = timeHr + fractionMin;
 double startDiff = fractionTime - (double)adjStartTime;
 double stopDiff = (double)adjStopTime - fractionTime;

 double yOffset;
 if (startDiff < 0.0) {
  *pY = yBase;
  return FALSE;
 } else if (stopDiff < 0.0) {
  startDiff = adjStopTime - adjStartTime;
  yOffset = startDiff * (double)hourHeight;
  *pY = yBase + yOffset;
  return FALSE;
 } else {
  yOffset = startDiff * (double)hourHeight;
  *pY = yBase + CMS_NINT(yOffset);
  return TRUE;
 }
}

void CDayView::DrawHourTriangle(CDC* pDC, CPoint vertex)
{
 CPoint vertices[3];
 vertices[0] = vertex;
 vertices[1].x = vertices[2].x = vertex.x - 25;
 vertices[1].y = vertex.y + 7;
 vertices[2].y = vertex.y - 7;

 CPen* pOldPen = pDC->SelectObject(&m_penBlackThin);
 CBrush* pOldBrush = pDC->SelectObject(&m_brushYellowSolid);

 pDC->Polygon(vertices, 3);

 pDC->SelectObject(pOldPen);
 pDC->SelectObject(pOldBrush);
}

void CDayView::DrawWorkPeriods(CDC* pDC)
{
 int brush; // Choice of brush
 int i, j;

// char message[100];
// char m2[100];

 m_allProjects.RemoveAll();
 int numProjects = 0;
 m_allWorkPeriods.RemoveAll();

 // Loop browses through the records of the array.
 // Add up the times for each unique project.
 int n = g_workPeriodSet.GetSize();
 if (n>0) {
  for (i=0; i<n; i++) {
   m_startTime = g_workPeriodSet[i].startTime;
   m_endTime = g_workPeriodSet[i].endTime;
   m_workDayNumber = g_workPeriodSet[i].workDayNumber;
   m_workPeriodNumber = g_workPeriodSet[i].workPeriodNumber;
   m_projectNumber = g_workPeriodSet[i].projectNumber;
   numProjects = m_allProjects.GetSize();
   if (numProjects > 0) {
    BOOL foundProject = FALSE;
    for (j=0; j<numProjects; j++) {
     if (m_projectNumber ==

m_allProjects[j].m_projectNumber) {
      foundProject = TRUE;
      m_allProjects[j].m_totalTime
           +=

(m_endTime - m_startTime);
      break;
     }
    }
    if (!foundProject) {
     m_allProjects.Add(COneProjectTotal(m_projectNumber));
     m_allProjects[numProjects].m_totalTime = m_endTime -

m_startTime;
    }
   } else {
    m_allProjects.Add(COneProjectTotal(m_projectNumber));
    m_allProjects[0].m_totalTime = m_endTime - m_startTime;
   }
   brush = this->ChooseProjectBrush(m_projectNumber);
   m_allWorkPeriods.Add(CWorkPeriodRect());
   m_allWorkPeriods[i].Initialize(this,
    &(m_fillBrush[brush]), &m_brushBlackHatched, &m_penBlackThin,
    m_startTime, m_endTime);
   m_allWorkPeriods[i].Draw(pDC);
// sprintf(message, "Work Day: %d; Period: %d; Project: %d.",
// m_workDayNumber, m_workPeriodNumber, m_projectNumber);
  }
// sprintf(m2, "There are %d work periods(s) today:\n", n);
// strcat(m2, message);
// AfxMessageBox(m2);
 } else {
// AfxMessageBox("No work periods today.");
 }

 this->DrawWorkPeriodLegends(pDC);
}

void CDayView::DrawWorkPeriodLegends(CDC *pDC)
{
 CString out;
 TEXTMETRIC textMetrics;
 RECT textRect;
 int textHeight;

 int x = legendLeft;
 int y = 10;

 pDC->SelectObject(&m_fontArialTitle);
 pDC->GetTextMetrics(&textMetrics);
 textHeight = textMetrics.tmHeight;

 textRect.left = x;
 textRect.top = y;
 textRect.right = x + legendWidth;
 textRect.bottom = y + textHeight;
 out = "Daily Totals";
 pDC->DrawText(out, &textRect, DT_CENTER|DT_TOP);

 y += textHeight + 10;

 pDC->SelectObject(&m_fontArialBody);
 pDC->GetTextMetrics(&textMetrics);
 textHeight = textMetrics.tmHeight;

 int n = m_allProjects.GetSize();
 if (n == 0) {
  textRect.top = y;
  textRect.bottom = y + 2*textHeight;
  out = "No work periods recorded.";
  pDC->DrawText(out, &textRect, DT_CENTER|DT_TOP);
  return;
 }

 textRect.left += legendBoxWidth + 2;
 for (int i=0; i<n; i++) {
  int pn = m_allProjects[i].m_projectNumber;
  int brush = this->ChooseProjectBrush(pn);
  this->DrawLegendBoxRect(pDC, &(m_fillBrush[brush]), y);

  textRect.top = y;
  textRect.bottom = y + 2*textHeight;
  this->DrawLegendProjectInfo(pDC, i, textRect);

  y += 40;
 }

 out = "All times are rounded\nto the nearest";
 out += this->GetRoundingFactor();
 out += "hour";

 pDC->SelectObject(&m_fontArialBody);
 pDC->GetTextMetrics(&textMetrics);
 textHeight = textMetrics.tmHeight;

 textRect.left = x;
 textRect.top = y;
 textRect.right = x + legendWidth;
 textRect.bottom = y + 2*textHeight;
 pDC->DrawText(out, &textRect, DT_CENTER|DT_TOP);
}

void CDayView::DrawLegendBoxRect(CDC* pDC, CBrush* pBrush, int top)
{
 int left = legendLeft;
 int right = left + legendBoxWidth;
 int bottom = top + legendBoxHeight;

 CPoint vertices[4];
 vertices[0].x = left;
 vertices[0].y = top;
 vertices[1].x = right;
 vertices[1].y = top;
 vertices[2].x = right;
 vertices[2].y = bottom;
 vertices[3].x = left;
 vertices[3].y = bottom;

 CPen* pOldPen = pDC->SelectObject(&m_penBlackThin);
 CBrush* pOldBrush = pDC->SelectObject(pBrush);

 pDC->Polygon(vertices, 4);

 pDC->SelectObject(pOldPen);
 pDC->SelectObject(pOldBrush);
}

void CDayView::DrawLegendProjectInfo(CDC* pDC, int projectIndex, RECT
textRect)
{
 char buff[100];
 CString out, rounded;
 BOOL found = FALSE;
 CString ID, name;

 int projectNumber = m_allProjects[projectIndex].m_projectNumber;
 int numProjects = g_projectList.GetSize();
 for (int i=0; i< numProjects; i++) {
  int pn = g_projectList[i].GetProjectNumber();
  if (projectNumber == pn) {
   found = TRUE;
   ID = g_projectList[i].GetIndexedMember(1);
   name = g_projectList[i].GetIndexedMember(2);
   break;
  }
 }
 ASSERT(found);

 COleDateTimeSpan elapsed = m_allProjects[projectIndex].m_totalTime;
 double time = elapsed.GetTotalSeconds() / 3600.0;
 CPayPeriodView::RoundWorkTime(time, &time, rounded);
 sprintf (buff, "%s, %s\n%s hour(s)", ID, name, rounded);
 out = buff;
 pDC->DrawText(out, &textRect, DT_CENTER|DT_TOP);
}

CString CDayView::GetRoundingFactor()
{
 CString out;
 switch(g_minWorkPeriod) {
 case MIN_WORK_PERIOD_SIX: // Rounded to 1/10s of an hour
  out = " 0.1 ";
  break;
 case MIN_WORK_PERIOD_TEN: // Rounded to 1/6s of an hour
  out = " 1/6 ";
  break;
 case MIN_WORK_PERIOD_FIFTEEN: // Rounded to 1/4s of an hour
  out = " 0.25 ";
  break;
 case MIN_WORK_PERIOD_THIRTY: // Rounded to 1/2s of an hour
  out = " 0.5 ";
  break;
 default:
  out = " ? ";
  break;
 }
 return out;
}

int CDayView::ChooseProjectBrush(int projectNum)
{
 int n = g_projectList.GetSize();
 for (int i=0; i<n; i++) {
  if (g_projectList[i].GetProjectNumber() == projectNum) {
   return g_projectList[i].GetColorIndex();
  }
 }

 return -1; // ERROR
}

int CDayView::Get_X_Origin()
{
 return xOrigin;
}

int CDayView::GetHourWidth()
{
 return hourWidth;
}

int CDayView::GetTotalDayWidth()
{
 return totalDayWidth;
}

/////////////////////////////////////////////////////////////////////////////

// CDayView diagnostics

#ifdef _DEBUG
void CDayView::AssertValid() const
{
 CScrollView::AssertValid();
}

void CDayView::Dump(CDumpContext& dc) const
{
 CScrollView::Dump(dc);
}

CClockWatcherDoc* CDayView::GetDocument()
{
 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CClockWatcherDoc)));
 return (CClockWatcherDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CDayView message handlers

BOOL CDayView::PreCreateWindow(CREATESTRUCT& cs)
{
 // TODO: Add your specialized code here and/or call the base class
 cs.style |= WS_BORDER | WS_VSCROLL | WS_HSCROLL;
 return CScrollView::PreCreateWindow(cs);
}

void CDayView::OnMouseMove(UINT nFlags, CPoint point)
{
 // TODO: Add your message handler code here and/or call default
 if (::IsWindow(m_tooltip.m_hWnd))
 {
  const CWorkPeriodRect* pRectHit = this->HitTest(point);

  if (!pRectHit || (pRectHit!=m_pRectHit))
  {
   // Use Activate() to hide the tooltip.
   m_tooltip.Activate(FALSE);
  }

  if (pRectHit)
  {
   m_tooltip.Activate(TRUE);
   m_pRectHit = pRectHit;
  }
 }

 // Pass on to the default handler.
 CScrollView::OnMouseMove(nFlags, point);
}

BOOL CDayView::OnToolTipNeedText(UINT id, NMHDR * pNMHDR, LRESULT *
pResult)
{
 BOOL bHandledNotify = FALSE;

 CPoint CursorPos;
 VERIFY(::GetCursorPos(&CursorPos));
 ScreenToClient(&CursorPos);

 CRect ClientRect;
 GetClientRect(ClientRect);

 // Make certain that the cursor is in the client rect, because the
 // mainframe also wants these messages to provide tooltips for the
 // toolbar.
 if (ClientRect.PtInRect(CursorPos))
 {
  TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;
  m_pRectHit = this->HitTest(CursorPos);

  if (m_pRectHit)
  {
   // Adjust the text by filling in TOOLTIPTEXT
   CString strTip;
   strTip.Format("Hello");
// strTip.Format("Center: (%d, %d)\nRadius: %d\nColor: (%d, %d,
%d)",
// Center.x, Center.y, m_pCircleHit->GetRadius(),
// (int)GetRValue(Color), (int)GetGValue(Color),

(int)GetBValue(Color));
   ASSERT(strTip.GetLength() < sizeof(pTTT->szText));
   ::strcpy(pTTT->szText, strTip);
  }
  else
  {
   pTTT->szText[0] = 0;
  }
  bHandledNotify = TRUE;
 }
 return bHandledNotify;
}

const CWorkPeriodRect* CDayView::HitTest(const CPoint& point)
{
 // Check in reverse order to deal with clipping.
 const CWorkPeriodRect* pRectHit = NULL;
 int numPeriods = m_allWorkPeriods.GetSize();
 if (numPeriods > 0) {
  for (int n=numPeriods-1; ((n>=0) && (pRectHit==NULL)); n--)
  {
   if (m_allWorkPeriods[n].HitTest(point))
   {
    pRectHit = &m_allWorkPeriods[n];
   }
  }
 }
 return pRectHit;
}

BOOL CDayView::PreTranslateMessage(MSG* pMsg)
{
 // TODO: Add your specialized code here and/or call the base class
 if (::IsWindow(m_tooltip.m_hWnd) && pMsg->hwnd == m_hWnd)
 {
  switch(pMsg->message)
  {
  case WM_LBUTTONDOWN:
  case WM_MOUSEMOVE:
  case WM_LBUTTONUP:
  case WM_RBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONUP:
  case WM_MBUTTONUP:
   m_tooltip.RelayEvent(pMsg);
   break;
  }
 }

 return CScrollView::PreTranslateMessage(pMsg);
}

void CDayView::OnInitialUpdate()
{
 CScrollView::OnInitialUpdate();
 this->CreateAllFonts();
 this->CreateAllPens();
 this->CreateAllBrushes();
 // TODO: calculate the total size of this view
 GetParentFrame()->RecalcLayout();

 // Initialize the calendar buttons.
 CPoint ptLeft(xOrigin+xBorder, yButton);
 CSize szLeft(arrowWidth, arrowBtnHeight);
 CRect rcLeft(ptLeft, szLeft);
 VERIFY(m_dayArrowLeft.Create(NULL,
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW,
    rcLeft, this, IDC_CALENDAR_LEFT));
 m_dayArrowLeft.LoadBitmaps
  (IDB_CALENDAR_UP_LT, IDB_CALENDAR_DN_LT, 0, 0);
 CPoint ptDay(xOrigin+xBorder+arrowWidth+3, yButton);
 CSize szDay(centerWidth, centerBtnHeight);
 CRect rcDay(ptDay, szDay);
 VERIFY(m_daySelect.Create(NULL,
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW,
    rcDay, this, IDC_CALENDAR_MONTH));
 m_daySelect.LoadBitmaps
  (IDB_CALENDAR_UP_CTR, IDB_CALENDAR_DN_CTR, 0, 0);
 CPoint ptRight(xOrigin+xBorder+arrowWidth+centerWidth+6, yButton);
 CSize szRight(arrowWidth, arrowBtnHeight);
 CRect rcRight(ptRight, szRight);
 VERIFY(m_dayArrowRight.Create(NULL,
    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW,
    rcRight, this, IDC_CALENDAR_RIGHT));
 m_dayArrowRight.LoadBitmaps
  (IDB_CALENDAR_UP_RT, IDB_CALENDAR_DN_RT, 0, 0);

 this->SetScrollBars();

 this->RebuildWorkPeriodSet();

 CRect ClientRect(0, 0, 1000, 1000);
 if (m_tooltip.Create(this, TTS_ALWAYSTIP) && m_tooltip.AddTool(this))
 {
  m_tooltip.SendMessage(TTM_SETMAXTIPWIDTH, 0, SHRT_MAX);
  m_tooltip.SendMessage(TTM_SETDELAYTIME, TTDT_AUTOPOP, SHRT_MAX);
  m_tooltip.SendMessage(TTM_SETDELAYTIME, TTDT_INITIAL, 200);
  m_tooltip.SendMessage(TTM_SETDELAYTIME, TTDT_RESHOW, 200);
 }
 else
 {
  TRACE("Error in creating ToolTip");
 }
}

void CDayView::OnUpdate(CView *pSender, LPARAM lHint, CObject *pHint)
{
 currentDay = this->GetDocument()->GetCurrentDay();
 currentMonth = this->GetDocument()->GetCurrentMonth();
 currentYear = this->GetDocument()->GetCurrentYear();
 adjStartTime = this->AdjustedStartTime();
 adjStopTime = this->AdjustedStopTime();

 this->SetScrollBars();
 this->RebuildWorkPeriodSet();

 CScrollView::OnUpdate(pSender, lHint, pHint);
}

void CDayView::SetScrollBars()
{
 CSize total = this->GetDrawingExtents();
 CSize page(total.cx/10, total.cy/10);
 CSize line( page.cx/10, page.cy/10);
 SetScrollSizes(MM_TEXT, total, page, line);
}

CSize CDayView::GetDrawingExtents()
{
 CSize extents;
 extents.cx = xOrigin + totalDayWidth + rightMargin + legendWidth;
 int numHrs = this->CalcNumHours();
 extents.cy = dayHeight + 10 +
     CMS_NINT(numHrs*hourHeight) + bottomMargin;
 return extents;
}

void CDayView::OnClickLeftArrow()
{
 this->DecrementDay();
 this->RefreshAllViews();
}

void CDayView::OnDblClickLeftArrow()
{
 this->OnClickLeftArrow();
}

void CDayView::OnClickRightArrow()
{
 this->IncrementDay();
 this->RefreshAllViews();
}

void CDayView::OnDblClickRightArrow()
{
 this->OnClickRightArrow();
}

void CDayView::OnClickDaySelect()
{
 CMenu thePopup;
 thePopup.LoadMenu(IDR_DAYLOG_POPUP); // returns 1 (success)
 CMenu *pSubPopup = thePopup.GetSubMenu(0);
 ASSERT (pSubPopup);

 CPoint pt;
 GetCursorPos(&pt);
 pSubPopup->TrackPopupMenu(TPM_CENTERALIGN|TPM_LEFTBUTTON,
    pt.x,pt.y,this,NULL); // "this" is this view
}

int CDayView::FebruaryDays(int theYear)
{
 if (theYear%4 == 0) {
  if (theYear%100 == 0) {
   if (theYear%1000 == 0) {
    return 29;
   } else {
    return 28;
   }
  } else {
   return 29;
  }
 } else {
  return 28;
 }
}

void CDayView::OnDayLog_Sun()
{
 this->ProcessWeekday(1);
}

void CDayView::OnDayLog_Mon()
{
 this->ProcessWeekday(2);
}

void CDayView::OnDayLog_Tue()
{
 this->ProcessWeekday(3);
}

void CDayView::OnDayLog_Wed()
{
 this->ProcessWeekday(4);
}

void CDayView::OnDayLog_Thu()
{
 this->ProcessWeekday(5);
}

void CDayView::OnDayLog_Fri()
{
 this->ProcessWeekday(6);
}

void CDayView::OnDayLog_Sat()
{
 this->ProcessWeekday(7);
}

void CDayView::ProcessWeekday(int menuDay)
{
 COleDateTime currentDate(currentYear, currentMonth, currentDay, 0, 0,
0);
 int currentWeekday = currentDate.GetDayOfWeek();
 int diff = menuDay - currentWeekday;
 if (diff != 0) {
  if (diff > 0) {
   for (int i=0; i<diff; i++) {
    this->IncrementDay();
   }
  } else {
   for (int j=0; j<abs(diff); j++) {
    this->DecrementDay();
   }
  }
 }

 this->RefreshAllViews();
}

void CDayView::IncrementDay()
{
 BOOL endOfMonth;

 currentDay++;

 if (currentMonth == 2) {
  if (currentDay > this->FebruaryDays(currentYear)) {
   endOfMonth = TRUE;
  } else {
   endOfMonth = FALSE;
  }
 } else {
  if (currentDay > numberOfDays[currentMonth-1]) {
   endOfMonth = TRUE;
  } else {
   endOfMonth = FALSE;
  }
 }

 if (endOfMonth) {
  currentMonth++;
  currentDay = 1;
  if (currentMonth > 12) {
   currentMonth = 1;
   currentYear++;
  }
 }

}

void CDayView::DecrementDay()
{
 currentDay--;

 if (currentDay<1) {
  currentMonth--;
  if (currentMonth < 1) {
   currentMonth = 12;
   currentYear--;
  }
  if (currentMonth == 2) {
   currentDay = this->FebruaryDays(currentYear);
  } else {
   currentDay = numberOfDays[currentMonth-1];
  }
 }
}

void CDayView::RefreshAllViews()
{
 this->GetDocument()->SetCurrentDay(currentDay);
 this->GetDocument()->SetCurrentMonth(currentMonth);
 this->GetDocument()->SetCurrentYear(currentYear);
// this->RebuildWorkPeriodSet();
 this->GetDocument()->SetModifiedFlag();
 this->GetDocument()->UpdateAllViews(NULL);
}

void CDayView::RebuildWorkPeriodSet()
{
 m_pQueryWkDays = this->GetDocument()->GetQueryWorkDaysSet();
 m_pQueryWkDays->RefreshWorkDay(currentYear, currentMonth, currentDay);
 m_pQueryWkDays->RebuidDayViewArray();
}

//////////////////////////////////////////////////////////////////////
// COneProjectTotal Class
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

COneProjectTotal::COneProjectTotal()
{
 m_projectNumber = -1;
 m_totalTime = 0;
}

COneProjectTotal::COneProjectTotal(int projectNumber)
{
 m_projectNumber = projectNumber;
 m_totalTime = 0;
}

COneProjectTotal::COneProjectTotal(int projectNumber, double totalTime)
{
 m_projectNumber = projectNumber;
 m_totalTime = totalTime;
}

COneProjectTotal::~COneProjectTotal()
{

}

//////////////////////////////////////////////////////////////////////
// CWorkPeriodRect Class
//////////////////////////////////////////////////////////////////////

CWorkPeriodRect::CWorkPeriodRect()
{

}

CWorkPeriodRect::~CWorkPeriodRect()
{

}

void CWorkPeriodRect::Initialize(CDayView* pParent,
         CBrush* pBrush, CBrush*

pHatched, CPen* pThin,
         COleDateTime startTime,

COleDateTime stopTime)
{
 int xOrigin = pParent->Get_X_Origin();
 int hourWidth = pParent->GetHourWidth();
 int totalDayWidth = pParent->GetTotalDayWidth();

 m_startTime = CWorkDayParams::COleDateTime2Double(startTime);
 m_endTime = CWorkDayParams::COleDateTime2Double(stopTime);

 int top;
 int bottom;
 int left = xOrigin + hourWidth + 2;
 int right = xOrigin + totalDayWidth;

 BOOL m_solidTop = pParent->TimeToY(m_startTime, &top);
 BOOL m_solidBottom = pParent->TimeToY(m_endTime, &bottom);

 m_rect = CRect(left, top, right, bottom);
 m_pBrush = pBrush;
 m_pPenBlackThin = pThin;
 m_pBrushBlackHatched = pHatched;
}

void CWorkPeriodRect::Draw(CDC* pDC) const
{
 CPoint vertices[4];
 vertices[0].x = m_rect.left;
 vertices[0].y = m_rect.top;
 vertices[1].x = m_rect.right;
 vertices[1].y = m_rect.top;
 vertices[2].x = m_rect.right;
 vertices[2].y = m_rect.bottom;
 vertices[3].x = m_rect.left;
 vertices[3].y = m_rect.bottom;

 CPen* pOldPen = pDC->SelectObject(m_pPenBlackThin);
 CBrush* pOldBrush = pDC->SelectObject(m_pBrush);

 pDC->Polygon(vertices, 4);
 if (!m_solidTop || !m_solidBottom) {
  pDC->SelectObject(m_pBrushBlackHatched);
  pDC->Polygon(vertices, 4);
 }

 pDC->SelectObject(pOldPen);
 pDC->SelectObject(pOldBrush);
};

BOOL CWorkPeriodRect::HitTest(const CPoint& point) const
{
 return m_rect.PtInRect(point);
};

////////////////////////////////////////////
//
// DayView.h
//
////////////////////////////////////////////

#if
!defined(AFX_DAYVIEW_H__41F27CF8_10AC_4E78_8490_5A65A7EA2CAB__INCLUDED_)

#define AFX_DAYVIEW_H__41F27CF8_10AC_4E78_8490_5A65A7EA2CAB__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// DayView.h : header file
//

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////

// COneProjectTotal class

class COneProjectTotal
{
 friend class CDayView;
public:
 COneProjectTotal();
 COneProjectTotal(int projectNumber);
 COneProjectTotal(int projectNumber, double totalTime);
 virtual ~COneProjectTotal();

private:
 COleDateTimeSpan m_totalTime;
 int m_projectNumber;
};

/////////////////////////////////////////////////////////////////////////////

// CWorkPeriodRect class

class CWorkPeriodRect
{
public:
 void Initialize (CDayView* pParent,
  CBrush* brush, CBrush* pHatched, CPen* pThin,
  COleDateTime startTime, COleDateTime stopTime);
 CWorkPeriodRect();
 virtual ~CWorkPeriodRect();
 void Draw(CDC* pDC) const;
 BOOL HitTest(const CPoint& point) const;

private:
 CBrush* m_pBrush;
 CBrush* m_pBrushBlackHatched;
 CPen* m_pPenBlackThin;
 CRect m_rect;
 double m_endTime;
 double m_startTime;
 BOOL m_solidTop;
 BOOL m_solidBottom;
};

/////////////////////////////////////////////////////////////////////////////

// CDayView class

class CDayView : public CScrollView
{
protected:
 DECLARE_DYNCREATE(CDayView)

// Attributes
public:

// Operations
public:
 CDayView(); // protected constructor used by dynamic creation

// Overrides
 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CDayView)
 public:
 virtual void OnInitialUpdate();
 virtual BOOL PreTranslateMessage(MSG* pMsg);
 protected:
 virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
 //}}AFX_VIRTUAL

// Implementation
public:
 CClockWatcherDoc* GetDocument();
 virtual void OnDraw(CDC* pDC); // overridden to draw this view
 virtual ~CDayView();
 int GetTotalDayWidth();
 int GetHourWidth();
 int Get_X_Origin();
 BOOL TimeToY(double time, int* pY);
 CBrush m_brushBlackHatched;
 CPen m_penBlackThin;
protected:
#ifdef _DEBUG
 virtual void AssertValid() const;
 virtual void Dump(CDumpContext& dc) const;
#endif

 // Generated message map functions
protected:
 const CWorkPeriodRect* HitTest(const CPoint& point);
 const CWorkPeriodRect* m_pRectHit;
 CToolTipCtrl m_tooltip;
 void OnDayLog_Sat();
 void OnDayLog_Fri();
 void OnDayLog_Thu();
 void OnDayLog_Wed();
 void OnDayLog_Tue();
 void OnDayLog_Mon();
 void OnDayLog_Sun();
 void OnClickDaySelect();
 void OnDblClickRightArrow();
 void OnClickRightArrow();
 void OnDblClickLeftArrow();
 void OnClickLeftArrow();
 void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
 //{{AFX_MSG(CDayView)
 afx_msg void OnMouseMove(UINT nFlags, CPoint point);
 //}}AFX_MSG
 BOOL OnToolTipNeedText(UINT id, NMHDR * pNMHDR, LRESULT * pResult);
 DECLARE_MESSAGE_MAP()
private:
 CString GetRoundingFactor();
 void DrawLegendProjectInfo(CDC* pDC, int projectIndex, RECT textRect);
 int legendWidth;
 CFont m_fontArialBody;
 CFont m_fontArialTitle;
 void CreateAllFonts();
 int legendBoxHeight;
 int legendBoxWidth;
 int legendLeft;
 void DrawLegendBoxRect(CDC *pDC, CBrush* pBrush, int top);
 void DrawWorkPeriodLegends(CDC *pDC);
 int bottomMargin;
 int rightMargin;
 CSize GetDrawingExtents();
 void SetScrollBars();
 int ChooseProjectBrush(int projectNum);
 CPen m_penBlackSolid;
 CPen m_penWhiteSolid;
 CPen m_penWhiteDotted;
 CBrush m_fillBrush[CMS_BRIGHT_COLORS];
 CBrush m_brushYellowSolid;
 void RebuildWorkPeriodSet();
 COleDateTime m_endTime;
 COleDateTime m_startTime;
 long m_projectNumber;
 CArray<COneProjectTotal,COneProjectTotal> m_allProjects;
 CArray<CWorkPeriodRect,CWorkPeriodRect> m_allWorkPeriods;
 long m_workPeriodNumber;
 long m_workDayNumber;
 CQueryWorkDaysSet* m_pQueryWkDays;
 void DrawWorkPeriods(CDC* pDC);
 int adjStopTime;
 int adjStartTime;
 void RefreshAllViews();
 void DecrementDay();
 void IncrementDay();
 void ProcessWeekday(int menuDay);
 int FebruaryDays(int theYear);
 void WriteDateHeader(CDC* pDC);
 int AdjustedStopTime();
 int AdjustedStartTime();
 void CreateAllBrushes();
 void DrawHourTriangle(CDC* pDC, CPoint vertex);
 void PlaceHourArrows(CDC* pDC);
 int CalcNumHours();
 void CreateAllPens();
 void DrawDayLogHours(CDC* pDC);
 void DrawDayLogBackground(CDC* pDC);
 void DrawDayLogBorders(CDC* pDC);
 void DrawDayLog(CDC* pDC);
 int hourWidth;
 int hourHeight;
 int taskWidth;
 int xBorder;
 int totalHeight;
 int totalDayWidth;
 int groupWidth;
 int centerWidth;
 int arrowWidth;
 int yButton;
 int arrowBtnHeight;
 int centerBtnHeight;
 int dayHeight;
 int xOrigin;
 int yOrigin;
 CBitmapButton m_dayArrowLeft;
 CBitmapButton m_daySelect;
 CBitmapButton m_dayArrowRight;
 int currentDay;
 int currentMonth;
 int currentYear;
};

#ifndef _DEBUG // debug version in ClockWatcherView.cpp
inline CClockWatcherDoc* CDayView::GetDocument()
   { return (CClockWatcherDoc*)m_pDocument; }
#endif

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before the previous

line.

#endif //
!defined(AFX_DAYVIEW_H__41F27CF8_10AC_4E78_8490_5A65A7EA2CAB__INCLUDED_)