/* Maintain a Gantt chart in a Window. The GanttWindow is
   responsible for drawing the project.

   GanttWindow descends from the Window class and inherits all
   of its methods and instance variables.
*/!!

inherit(Window, #GanttWindow, #(project     /* the project */
activities  /* in the project */
barWidth    /* width of a unit */
barHeight   /* based on scrnsize */
activHeight /* bar + dead space */), 2, nil)!!

now(GanttWindowClass)!!

/* Return the name of this class's MS-Windows window class
  either for registration or new window creation. */
Def wndClass(self)
{ ^"GanttWindow"  }!!

/* Create a new window class Struct.
   Change the cursor to a cross.  */
Def newWClass(self, lpCl, lpIcon | wc)
{ wc := newWClass(self:WindowClass, lpCl, lpIcon);
  putWord(wc, Call LoadCursor(0, IDC_CROSS), 14);
  ^wc;
} !!

now(GanttWindow)!!

/* See if the user has double clicked on a bar.
   If so, bring up a dialog for editing. */
Def  WM_LBUTTONDBLCLK(self, wP, lP | idx)
{
  idx:= (y(asPoint(lP)) - barHeight) / activHeight;
  if idx <= size(activities) - 1 
    if editInfo(activities[idx]) == IDOK
      dirty(self);
    endif;
  else
    beep();
  endif;
}!!

/* For a Task, draw a critical bar.  Make sure we don't draw
   draw over the titles if the node hasn't been hooked up yet.
   Uses early binding, direct Windows calls for speed. */
Def  drawTask(self, aNode, x, y, hDC | startX, hBrush)
{
   startX := max(GW_STARTX, x - asInt(getTime(aNode)*barWidth));
   
   hBrush := Call CreateSolidBrush(CritColor);
   Call SelectObject(hDC, hBrush);   
   Call FillRect(hDC, rect(startX:Int, y, x, y+barHeight), hBrush);
   Call Rectangle(hDC, startX, y, x, y+barHeight);
   Call DeleteObject(Call SelectObject(hDC, hBrush));
}!!

/* Create the window using a regular window style. */
Def  create(self, par, wName, rect, style)
{ 
  ^create(self:Window, par, wName, rect, 
     WS_POPUPWIND);
}!!

/* If we close, be sure to tell the parent. 
   For testing a standalone Gantt chart delete this method. */
Def  close(self)
{
  close(self:Window);
  closeGantt(parent);  /* assumes parent is a ProjWindow */
}!!

/* Make sure the parent window knows that the project
   has changed. */
Def  dirty(self)
{
  invalidate(self);
  invalidate(parent);
}!!

/* Set up a dummy project, menus and the method table. */
Def  init(self)
{  
  setProject(self, new(Project));
  recalc(project);  
  setMenus(self);
  setResolution(self);
}!!

/* Set the resolution for the size of bars, spacing etc.
   For CGA, bars should be smaller. */
Def  setResolution(self | hDC, ts)
{
  hDC := getContext(self);
  ts := textSize(self, hDC);
  releaseContext(self, hDC);
  barHeight := y(ts);
  activHeight := asInt(1.25 * y(ts));
}!!

/* Handle menu commands. */
Def  command(self, wp, lp)
{
  select
    case wp == PW_HELP
      help(self);
    endCase
    case wp == PW_VIEW_GANTT  /* open or close, same key */
      close(self);
    endCase
    case wp == PW_FILE_PRINT_GRAPH
      printWindow(self, 1);
    endCase
    case wp == PW_CLIP
      clipWindow(self, 1);
    endCase
  endSelect;
}!!

/* Load menu resources. */
Def  setMenus(self)
{
  loadMenu(self, "GWMenus");
  setMenu(self, hMenu);
}
!!

/* Display help information from resource file. */
Def  help(self | dlg)
{
  dlg := new(Dialog);
  checkRunModal(dlg, GW_HELP_BOX, self);
}!!

/* Draw a Milestone diamond in the window. */
Def  drawMilestone(self, aNode, x, y, hDC)
{
    Call SelectObject(hDC, stock(WHITE_BRUSH));
    draw(new(WinPolygon,
      tuple(
        point(x, y),                 /* top */
        point(x - GW_DMWIDTH, 
              y + barHeight/2),      /* left */
        point(x, y + barHeight),     /* bottom */
        point(x + GW_DMWIDTH,
              y + barHeight/2)       /* right */
      )), hDC);
}!!

/* Draw a slack bar in the window. 
   Uses direct Windows call for speed. */
Def  drawSlackBar(self, aNode, x, y, hDC | hBrush)
{
   if CritColor == BLACK then
     Call FrameRect(hDC, rect(x:Int, y, 
                x + asInt(getSlack(aNode)*barWidth),
                y + barHeight), stock(BLACK_BRUSH));
   else                
     Call FillRect(hDC, rect(x:Int, y, 
                x + asInt(getSlack(aNode)*barWidth),
                y + barHeight), stock(GRAY_BRUSH));
   endif;   
}!!

/* Draw text info in the window. */
Def  drawTextInfo(self, aNode, x, y, hDC | str)
{
      if critical(aNode)
        Call SetTextColor(hDC, CritColor);
        str := "*" + getName(aNode);
      else
        Call SetTextColor(hDC, SlackColor);
        str := " " + getName(aNode);
      endif;
      Call TextOut(hDC, x, y, str, size(str));  
}!!

/* Set the bar width so that the chart fits in the window. 
   Adjust in case it's very large or very small. */
Def  setBarWidth(self, time)
{
  barWidth := asInt((right(clientRect(self)) - GW_STARTX) / 
                (time + 1));
  barWidth := min(75, max(2, barWidth));
}!!

/* Set the project. */
Def  setProject(self, aProject)
{
  project := aProject;
  recalc(project);
}!!

/* Draw the axis for the window. 
   Uses direct Windows calls for speed. */
Def  drawAxis(self, hDC | endX, endY, str)
{
    endX := GW_STARTX + barWidth * 
               (max(2,asInt(getTime(project)+getSlack(project)))) ;  
    endY := activHeight * (size(nodes(project))+1); 
       
    /* draw a rectangle for x and y axis */ 
    
    Call Rectangle(hDC, GW_STARTX, GW_STARTY, endX, endY+1);
        
    /* draw notches on the x axis */    
    do(overBy(GW_STARTX, endX, barWidth),
      {using(i) 
        Call MoveTo(hDC, i, GW_STARTY);           /* top */
        Call LineTo(hDC, i, GW_STARTY + GW_NOTCH);
        Call MoveTo(hDC, i,endY);                 /* bot */
        Call LineTo(hDC, i, endY - GW_NOTCH);
    });
    str := asString(getEarlyStart(project));    
    Call TextOut(hDC, GW_STARTX, endY + 10, str, size(str));  
}!!

/* Respond to MS-Windows messages to paint the window. 
   Show the project as a Gantt chart. 
   Draw the axis, then for each node draw the graphics. */
Def  paint(self, hDC | y, t)
{
  setBarWidth(self, asInt(getTime(project)+getSlack(project)));
  activities := sortedActivities(project);
  
  drawAxis(self, hDC);
  y := barHeight;         /* starting positions for bars */
  
  do(activities,
    {using(aNode) 
   
     drawTextInfo(self, aNode, 10, y, hDC);
     
      /* make a temp to avoid calculating starting position */
      t := max(GW_STARTX, GW_STARTX + barWidth *
            asInt(getEarlyStart(aNode)-getEarlyStart(project)+getTime(aNode)));
      
      /* draw a slack bar if necessary. */  
      if getSlack(aNode) > 0  
        drawSlackBar(self, aNode, t, y, hDC)
      endif;

      draw(aNode, self, t, y, hDC);  /* node knows how */

      y := y + activHeight;     /* move down for next bar */
  });
}!!


