/* Maintain a Project chart in a Window. The ProjWindow is
   responsible for all drawing of the project. The window 
   has full scrolling and supports a keyboard interface.
   If topMethod or botMethod are set, then the corresponding
   method will be executed for each activity while displaying
   the project.

   ProjWindow descends from class Window and inherits all of
   its instance variables and methods.

   All strings are defined as resources for easy translation.
*/!!

inherit(Window, #ProjWindow, #(project     /* the project */
fileName    /* of the project */
actions     /* menu actions table */
dirty       /* true if not saved */
view        /* normal or zoomed */
boxHeight   /* based on scrnsize */
boxWidth    /* adjustable for zoom */
boxHSpace   /* adjustable */
activWidth  /* adjustable */
hScrollPos  /* horiz scroll posn */
vScrollPos  /* vert scroll posn */
maxVisCol   /* max visible col */
maxVisRow   /* max visible row */
methTable   /* table of methods */
topMethod   /* offset into table */
botMethod   /* offset into table */
gw          /* a GanttWindow */
/* these are new for dragging */
dragDC      /* for drawing */
anchorPt    /* starting point */
oldPt       /* end point */
), 2, nil)!!

now(ProjWindowClass)!!

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

/* 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(ProjWindow)!!

/* Allow the user to change the printer setup. */
Def printerSetup(self | prn)
{ 
  prn := new(Printer);
  getPrinterParms(prn);
  deviceMode(prn, self);
}
!!

/* Respond to menu choice to run a program. 
   By default, run MSDOS executive shell.*/
Def run(self | dlg, progName)
{
  dlg := new(InputDialog, "Run","Program name:","MSDOS.EXE");
  if runModal(dlg, INPUT_BOX, self) == IDOK
    progName:=getText(dlg);
    exec(progName);
  endif;
}!!

/* Handles menu choice to set colors. Global variables
   CritColor and SlackColor determine the application
   colors. */
Def toggleColor(self)
{
  if CritColor <> BLACK
    unCheckMenuItem(self, PW_COLOR);
    CritColor := BLACK;
    SlackColor := BLACK;
  else
    checkMenuItem(self, PW_COLOR);
    CritColor := RED;
    SlackColor := BLUE;
  endif;
  invalidate(self);
  if gw invalidate(gw); endif;
}!!

/* Create a new Milestone */
Def newMilestone(self)
{
  newActivity(self, Milestone);
}!!

/* Create the window using a "main" window style. */
Def create(self, par, wName, rect, style)
{ 
  ^create(self:Window, nil, wName, rect, 
     WS_OVERLAPPEDWINDOW bitOr WS_VSCROLL bitOr WS_HSCROLL);
}!!

/* Set up a dummy project, menus and the method table. */
Def init(self)
{  
  setProject(self, new(Project));
  setAutoCalc(project, true);  
  setView(self, #normal);
  setScrollBars(self);
  setMenus(self);
  checkMenuItem(self, PW_AUTOCALC);
  checkMenuItem(self, PW_COLOR);
  setMethTable(self);
}!!

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

/* Set the resolution for the size of nodes, spacing etc.
   based on the font text size.  There are two views,
   normal and small.  Both are based on text font size so
   that they will display adequately with any resolution. */
Def setView(self, aView | hDC, ts)
{
  view := aView;
  hDC := getContext(self);    /* so we can get text size */
  ts := textSize(self, hDC);  /* x is width, y is height */
  releaseContext(self, hDC);
  if view == #small
    boxWidth := x(ts) * 4;
    boxHSpace := x(ts)/2 + 1;
    boxHeight := y(ts) + 4;
  else
    boxWidth := asInt(x(ts)*8);
    boxHSpace := x(ts) + 2;
    boxHeight := y(ts) * 2;   
  endif;
  activWidth := boxWidth + boxHSpace;
  reSize(self, 0, 0L);        /* so scrolling is adjusted */
}!!

/* Set the scroll bar ranges and initial positions. */
Def setScrollBars(self)
{ 
  Call SetScrollRange(hWnd, SB_HORZ, 0, PW_MAX_COLS, 0);
  Call SetScrollRange(hWnd, SB_VERT, 0, PW_MAX_ROWS, 0);
  hScrollPos := vScrollPos := 0; 
}!!

/* Set up the method table used for display options. 
   Each of these methods should be applicable to all
   activities in the project, that is, both Milestones
   and Tasks. */
Def setMethTable(self)
{
    methTable := #(#getTime, 
                   #getSlack, 
                   #getCost,
                   #getEarlyStart,
                   #getEarlyFinish,
                   #getLateStart,
                   #getLateFinish);
  topMethod := 3;
}!!

/* Set the method to display info below each box. 
   The offset refers to the position in the
   methTable. */
Def setBotMethod(self, offset)
{
  botMethod := offset;
}!!

/* Set the method to display info atop each box. 
   The offset refers to the position in the
   methTable. */
Def setTopMethod(self, offset)
{
  topMethod := offset;
}!!

/* Add an about box and load menu resources.
   Set up the menu actions table. Each entry
   consists of a constant ID and a message
   that will be performed. */
Def setMenus(self)
{
  addAbout(self);
  loadMenu(self, "PWMenus");
  setMenu(self, hMenu);

  actions := new(Dictionary, 30);

  add(actions, PW_FILE_NEW, #fileNew);
  add(actions, PW_FILE_OPEN, #fileOpenAs);
  add(actions, PW_FILE_SAVE, #fileSave);
  add(actions, PW_FILE_SAVEAS, #fileSaveAs);
  add(actions, PW_FILE_PRINT, #print);
  add(actions, PW_FILE_PRINT_GRAPH, #printGraphs);
  add(actions, PW_FILE_SETTINGS, #printerSetup);
  add(actions, PW_RUN, #run);
  add(actions, PW_FILE_QUIT, #close);
  add(actions, PW_CLIP, #clipChart);
  add(actions, PW_ABOUT_ACTOR, #aboutActor);
  add(actions, PW_DEL_ACTIVITY, #deleteActivity);
  add(actions, PW_DEL_RESOURCE, #deleteResource);
  add(actions, PW_NEW_MSTONE, #newMilestone);
  add(actions, PW_NEW_TASK, #newTask);
  add(actions, PW_NEW_PERT, #newPERTTask);
  add(actions, PW_DISPLAY, #displaySettings);
  add(actions, PW_COLOR, #toggleColor);
  add(actions, PW_AUTOCALC, #autoCalc);
  add(actions, PW_CALC, #recalc);
  add(actions, PW_SHOWROOM, #showRoom);
  add(actions, PW_ZOOM, #zoom);
  add(actions, PW_VIEW_SUMMARY, #viewSummary);
  add(actions, PW_VIEW_ACTIVITIES, #showActivities);
  add(actions, PW_VIEW_GANTT, #gantt);
  add(actions, PW_VIEW_RESOURCES, #showResources);
  add(actions, PW_VIEW_RESOURCE, #editResource);

    /* keyboard accelerator commands */
    
  add(actions, PW_HELP, #help);
  add(actions, EDIT_HOME, #home);
  add(actions, PW_COMMAND_MODE, #commandMode);
}!!

/* Convert window coordinates to relative display coordinates. */
Def windowToDisplay(self, wPoint)
{
  ^point((x(wPoint) - boxHSpace) / activWidth  + hScrollPos,
         (y(wPoint) - boxHeight) / (boxHeight*2) + vScrollPos);
}!!

/* Convert relative display entries to window coordinates. 
   Uses dot notation for speed. */
Def displayToWindow(self, dPoint)
{
   ^point(boxHSpace + (dPoint.x - hScrollPos) * activWidth,
          boxHeight + (dPoint.y - vScrollPos) * boxHeight*2);          
}!!

/* The name of application, used for captions. */
Def  appName(self)
{ 
  ^loadString(PW_APPNAME);
}!!

/* Check if there is a file error and display an error box. 
   Return the error number, 0 for no error. */
Def checkError(self, aFile | errNo)
{
   errNo := getError(aFile);
   if errNo <> 0
     beep();
     errorBox(loadString(PW_FILEERR1),
              loadString(PW_FILEERR2) + asString(aFile) +
              loadString(PW_FILEERR3) + asString(errNo) +
              loadString(50+errNo));
   endif;
   ^errNo;
}!!

/* Returns true if the node is visible in the window.
   Used to speed up redrawing. Uses dot notation for speed. */
Def visible(self, aNode)
{ 
  ^between(aNode.x, hScrollPos, maxVisCol + hScrollPos)
     cand between(aNode.y, vScrollPos, maxVisRow + vScrollPos);
}!!

/* Set up the cursor for use even if there is no mouse. */
Def gotFocus(self, prevHwnd)
{ 
  Call ShowCursor(1);
}!!

/* Return to normal use of cursor.  */
Def losingFocus(self, hWndNew)
{ 
  Call ShowCursor(0);
} !!

/* Check if the project has been saved. */
Def shouldClose(self)
{ 
  ^checkClean(self)
}!!

/* Make sure the window knows that the project has changed. */
Def dirty(self)
{
  invalidate(self);
  dirty := true;
  if gw
    invalidate(gw);
  endif;
}!!

/* Returns boolean value true if the project has
   not changed since last save.  If it is dirty,
   the user is prompted for confirmation.  */
Def checkClean(self)
{
  ^ not(dirty) cor 
     yesNoBox(loadString(PW_WARNING),
              loadString(PW_DISCARD)) == IDYES;
}!!

/* Get the offset of the botMethod. */
Def getBotMethod(self)
{
  ^botMethod;
}!!

/* Get the offset of the topMethod. */
Def getTopMethod(self)
{
  ^topMethod;
}!!

/* Resize the window.  Adjust visible rows and cols. */
Def reSize(self, wp, lp)
{ 
  maxVisRow := bottom(clientRect(self)) / (boxHeight*2);
  maxVisCol := right(clientRect(self)) / activWidth;
}!!

/* Respond to MS-Windows messages to paint the window. 
   Show the project as a network chart.
   Draw each visible node in its proper position. 
   Display the name and any other info required.
   Then draw the lines to connect the outputs.
   
   The displayToWindow message converts a node's
   logical display position to windows coordinates.
   
   This could be further speeded up by repainting 
   only the invalid rectangle.
*/
Def paint(self, hDC |wPoint, x, y)
{
  do(nodes(project), 
   {using(aNode)
   
   wPoint := displayToWindow(self, pos(aNode));
   x := x(wPoint);    /* horiz windows posn */
   y := y(wPoint);    /* vert windows posn */
    
   if visible(self, aNode)
     draw(aNode, self, x, y, hDC);  /* node knows how to draw */ 
     drawTextInfo(self, aNode, x, y, hDC);
   endIf;
   /* always draw connections since they may be visible */
   drawConnections(self, aNode, x, y, getOutputs(aNode), hDC);
  }); 
}!!

/* Draw lines connecting the nodes in the window. 
   Uses early binding, dot notation and direct
   Windows calls for speed. */
Def drawConnections(self, aNode, x, y, connectedNodes, hDC | wPoint)
{
     do(connectedNodes,
       {using(output)  
        Call MoveTo(hDC,  x + boxWidth, y + boxHeight/2);
        wPoint := displayToWindow(self:ProjWindow, pos(output));
        Call LineTo(hDC, wPoint.x, wPoint.y + boxHeight/2);

        if critical(aNode:Activity) cand critical(output:Activity)  
          Call MoveTo(hDC,  x + boxWidth, y + boxHeight/2+1);
          Call LineTo(hDC, wPoint.x, wPoint.y + boxHeight/2+1);
        endif;       
     });   
}!!

/* Draw text info in the window.  If the small view is
   being used, show less information.
   Uses early binding and dot notation for speed. */
Def drawTextInfo(self, aNode, x, y, hDC | ts, str, strSize, offsetY, offsetX)
{
 ts := textSize(self, hDC);
 offsetY := (boxHeight - ts.y) / 2;
 strSize := boxWidth / ts.x;
 if view == #small
   offsetX := 2;   /* to make milestones look ok */
 else
   offsetX := 0;
 endif;

 str := subString(aNode.name, 0, strSize-1);
 Call SetTextColor(hDC, SlackColor);
 Call TextOut(hDC, x + offsetX + 2, y + offsetY, str, size(str));     
 
 if critical(aNode)
   Call SetTextColor(hDC, CritColor);
 endif;

 if topMethod     /* display above box */
   str := subString(asString(perform(aNode, methTable[topMethod])),0,strSize);
   Call TextOut(hDC, x + 2, y - ts.y, str, size(str));
 endif;
 
 if view ~= #small
 cand botMethod     /* display below box */
     str :=  subString(asString(perform(aNode, methTable[botMethod])),0,strSize);
     Call TextOut(hDC, x + 2, y + boxHeight, str, size(str));
 endif;
}!!

/* Draw a task in the window. 
   Uses early binding and direct Windows call for speed. */
Def drawTask(self, aNode, x, y, hDC)
{
  Call Rectangle(hDC, x, y, x + boxWidth, y + boxHeight); 
  if critical(aNode:Activity)
    Call Rectangle(hDC, x+1, y+1, x + boxWidth-1, y + boxHeight-1);
  endif;
}!!

/* Draw a milestone in the window. 
   Uses early binding and direct Windows call for speed. */
Def drawMilestone(self, aNode, x, y, hDC)
{
  Call RoundRect(hDC, x, y, x + boxWidth, y + boxHeight, 20,20);
  if critical(aNode:Activity)
    Call RoundRect(hDC, x+1, y+1, x + boxWidth-1, y + boxHeight-1, 17,17);
  endif;     
}!!

/* Handle menu events, accelerator keys.
   Lookup the ID in the actions table, if found
   perform it.  */
Def command(self, wp, lp)
{
  if actions[wp]
    perform(self, actions[wp]);
  else  /* these keys might be accelerators */
    if wp == VK_UP  cor wp == VK_DOWN cor
       wp == VK_RIGHT cor wp == VK_LEFT
      WM_KEYDOWN(self, wp, lp);
    else
      beep();  /* internal error! */
      errorBox(loadString(PW_ERROR1),
        loadString(PW_ERROR2) + asString(wp));
    endif;
  endif;
}!!

/* Go into "command mode" in response to a slash key accelerator.
   This simulates Lotus 1-2-3 style commands by sending an
   ALT key sysCommand message. */
Def commandMode(self)
{ 
  WM_SYSCOMMAND(self, 0xF100, 0L);
}!!

/* Get a filename from the user in responds to menu event.
   If the file has changed, check with the user.
   Send a fileOpen message to do the work. */
/* Respond to menu choice to create a new project. */
Def fileNew(self)
{
  if checkClean(self)
    if gw
      close(gw);
    endif;
    fileName := nil;
    dirty := nil;  
    setProject(self, new(Project));
    checkMenuItem(self, PW_AUTOCALC);
    setAutoCalc(project, true);
    setText(self, appName(self));   
    editInfo(project);
    invalidate(self);
  endif;
}!!

/* Prompt the user for the filename to open.
   Use the new combo box type of file dialog. */
Def fileOpenAs(self | dlg, file, reader)
{
  if checkClean(self)
    dlg := new(FileDialog, "*."+loadString(PW_EXTENSION));
    if runModal(dlg, FILE_BOX, self) == IDOK
      fileName := loadFile(dlg);    
      fileOpen(self, fileName);
    endif;
  endif;
}!!

/* Open the file named fName and read a project from disk.
   If a DOS error occurs (like file not found) then stop.
   Uses the Language Extensions I object storage facility. 
   This method copies the file into a stream so that it
   can read faster.  Also, the old project, file and stream
   are set to nil as soon as possible to free up memory. */
Def fileOpen(self, fName | dlg, file, strm, reader)
{
  showWaitCurs();                       /* this takes a while */
  if gw
    close(gw);                          /* close old GanttWindow */
  endif;      
  file := new(File);                    /* open the file etc. */
  setName(file, fName);
  open(file, 0);
  if checkError(self, file) == 0        /* no DOS errors */
    project := nil;                     /* free up memory */        
  
    strm := streamOver(copyFrom(file, 0, length(file))); 
    close(file);
    setText(self, appName(self)+": " + fName);  
    reader := new(StoredObjectReader);
    project := readFrom(reader, strm);  /* read the project */
    strm := nil;                        /* free up memory */
    setAutoCalc(project, true);         /* other settings */
    checkMenuItem(self, PW_AUTOCALC);
    dirty := nil;
    invalidate(self);                   /* redraw the window */
  else
    fileName := nil;                    /* due to DOS error */
  endif;
  showOldCurs();                        /* all done */
}!!

/* Respond to menu choice to save the file.  If the file
   isn't yet named, prompt the user for a name. */
Def fileSave(self)
{
  if fileName
    fileSaveIt(self, fileName);
  else
    fileSaveAs(self);
  endif;
}!!

/* Save the project to disk with a new name. */
Def fileSaveAs(self | dlg)
{
  if not(fileName)
     fileName := getName(project)+"."+loadString(PW_EXTENSION);
  endif;
  dlg := new(InputDialog, loadString(PW_APPNAME),
             loadString(PW_SAVEPROJ), fileName);
  if runModal(dlg, INPUT_BOX, self) == IDOK         
     fileName := getText(dlg);
     fileSaveIt(self, fileName);
  endif;
}!!

/* Save the project to disk with the filename fName. 
   If a DOS error occurs (like file not found) stop. 
   Uses the Language Extensions I object storage facility. 
   Note: this will not save any global variables!  */
Def fileSaveIt(self, fName | file)
{
  showWaitCurs();                          /* this takes a while */
  file := new(File);                       
  setName(file, fName);
  create(file);
  if checkError(self, file) == 0           /* no DOS error */
     setText(self, appName(self) + ": " + fileName);
    storeOn(project, file, nil);           /* write to file */
    close(file);
    dirty := false;
  else
    fileName := nil;                       /* due to DOS error */
  endif;
  showOldCurs();
}!!

/* Print a text summary of the project.
   Uses the Printer class.  */
Def print(self | prn)
{
  prn := new(Printer);
  showWaitCurs();                              /* takes a while */
  if start(prn, "Project", self)               /* start printing */
     do(getInfoLines(project),
       {using(line)
        printLine(prn, line);
     });
     printLine(prn, " ");
     printLine(prn, " ");
     printLine(prn, loadString(PW_ACTIVT1)+":");
     printLine(prn, loadString(PW_ACTIVT2));
     printLine(prn, loadString(PW_ACTIVT3));
     printLine(prn, "--------------------------------------------");
     do(sortedActivities(project),
       {using(aNode)
        printLine(prn, getInfoLine(aNode));
     });
     if size(resources(project)) > 0
       printLine(prn, " ");
       printLine(prn, " ");
       printLine(prn, loadString(PW_REST1) + ":");
       printLine(prn, loadString(PW_REST2));
       printLine(prn, loadString(PW_REST3));
       printLine(prn, "------------------------------------------");
       do(resources(project),
         {using(aRes)
          printLine(prn, getInfoLine(aRes));
       });
     endif;
     finish(prn);                             /* all done */
     showOldCurs();
  else
    showOldCurs();
    beep();
    errorBox(loadString(PW_PRINTERR1),loadString(PW_PRINTERR2));
  endif;
}!!

/* Print graphics charts */
Def printGraphs(self)
{
  printWindow(self, 1);
}!!

/* Copy bitmap to clipboard */
Def clipChart(self)
{
  clipWindow(self, 1);
}

/* Create a new Milestone */
Def newMilestone(self)
{
  newActivity(self, Milestone);
}!!

/* Create a new Task */
Def newTask(self)
{
  newActivity(self, Task);
}!!

/* Create a new PERTTask */
Def newPERTTask(self)
{
  newActivity(self, PERTTask);
}!!

/* Create a new activity based on a menu choice. 
   The nodeClass should be passed in as Task, Milestone etc.
   Return the new activity or nil if canceled. */
Def newActivity(self, nodeClass | activity)
{  
  activity := new(nodeClass);
  setNetwork(activity, project);
  if editInfo(activity) == IDOK   /* let user connect it */
    if pos(activity) = 0@0        /* still not connected */
      activity.y := 1;            /* safe location */
      resetPosn(getNetwork(activity), activity, 0@0);
    endif;
    dirty(self);                  /* redraw etc. */
    ^activity;
  endif;
  ^nil;
}!!

/* Delete a resource in response to menu choice. 
   Returns the deleted resource or nil if canceled. */
Def deleteResource(self | dlg, name, res)
{ 
  dlg := new(InputDialog, loadString(PW_DELRES1),
      loadString(PW_DELRES2), "");
  if runModal(dlg, INPUT_BOX, self) == IDOK
    name := getText(dlg);
    if res := checkResExists(project, name) 
       delete(res, project);
       dirty(self);
       ^res;
    endif;    
  endif;
  ^nil;
}!!

/* Delete an activity in response to menu choice. 
   Returns the deleted node or nil if canceled. */
Def deleteActivity(self | dlg, name, node)
{ 
  dlg := new(InputDialog, loadString(PW_DELACT1),
      loadString(PW_DELACT2), "");
  if runModal(dlg, INPUT_BOX, self) == IDOK
    name := getText(dlg);
    if node := checkNodeExists(project, name) 
       delete(node);
       dirty(self);
       ^node;
    endif;
  endif;
  ^nil
}!!

/* Edit a particular resource in response to menu choice.
   For now, ask the user which one. Return the resource
   or nil if canceled. */
Def editResource(self | dlg, res)
{
 dlg := new(InputDialog, loadString(PW_VIEWRES1), loadString(PW_VIEWRES2),"");
 if runModal(dlg, INPUT_BOX, self) == IDOK
   if res := checkResExists(project, getText(dlg))
      editInfo(res);
      dirty(self);
      ^res;
   endif;
 endif;  
 ^nil;
}!!

/* Show all of the resources as in a TextWindow. */
Def showResources(self | tw)
{
  tw := new(TextWindow, self, nil, loadString(PW_REST1), nil);
  show(tw, 1);  
  printLine(tw, loadString(PW_SHOWRES2));
  printLine(tw, "-----------------------------------------------");
  do(resources(project),
    {using(res)  
     printLine(tw, getInfoLine(res));
  });
}!!

/* Show all of the activities in a TextWindow. */
Def showActivities(self | tw)
{
  tw := new(TextWindow, self, nil, loadString(PW_ACTIVT1), nil);
  show(tw, 1);  
  printLine(tw, loadString(PW_SHOWACT2));
  printLine(tw, "------------------------------------------");
  do(sortedActivities(project),
    {using(aNode)  
     printLine(tw, getInfoLine(aNode));
  });
}!!

/* Respond to menu choice to edit the project */
Def viewSummary(self)
{
  editInfo(project);
}!!

/* If the user presses the home key, reset scroll posn. */
Def home(self)
{
  hScrollPos := vScrollPos := 0;
  Call SetScrollPos(hWnd, SB_HORZ, hScrollPos, 1);
  Call SetScrollPos(hWnd, SB_VERT, vScrollPos, 1);
  invalidate(self);
}!!

/* If the right button is pressed, toggle the view
   to allow zoom in and out */
Def zoom(self)
{
   if view == #normal
        setView(self, #small);
   else
        setView(self, #normal);
   endif;
   invalidate(self);
}!!

/* Show the amount of memory available. */
Def showRoom(self | temp)
{ 
 temp := Call GlobalCompact(Call GlobalCompact(0)+16) / 1024;

 errorBox(loadString(PW_SHOWROOM1),
   asString(temp) + loadString(PW_SHOWROOM2));
}!!

/* Respond to menu choice to recalculate. */
Def recalc(self)
{
  showWaitCurs();
  recalc(project);
  invalidate(self);
  if gw
   invalidate(gw);
  endif;
  showOldCurs();
}!!

/* The ganttWindow has closed.  */
Def closeGantt(self)
{
  gw := nil;
  enableMenuItem(self, PW_VIEW_GANTT);
}!!

/* Respond to menu choice to set display options.  The user
   can select information to be displayed above or below
   the nodes in the window.  */
Def displaySettings(self | dlg)
{
  dlg := new(PWSetDialog);
  if runModal(dlg, SETTING_BOX, self) == IDOK
     invalidate(self);
  endif;
}!!

/* Handle menu choice to create a Gantt chart. 
   Create the window large enough to hold all of
   the activities, plus some extra space. */
Def gantt(self | height)
{
  if screenSize()=640@200
    height := 10
  else
    height := 20;
  endif;
  gw := new(GanttWindow, self, nil, loadString(PW_GANTT),
            rect(asInt(x(screenSize())/3), 
              max(y(screenSize())-(size(project)+5)*height,10),
              x(screenSize()), y(screenSize())));
  setProject(gw, project);
  show(gw,1);
  grayMenuItem(self, PW_VIEW_GANTT);
}!!

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

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

/* Handles menu choice to set autocalc. */
Def autoCalc(self)
{
  if autoCalc(project)
    unCheckMenuItem(self, PW_AUTOCALC);
    setAutoCalc(project, false);
  else
    checkMenuItem(self, PW_AUTOCALC);
    setAutoCalc(project, true);
    recalc(self);
  endif;
}!!

/* See if the user has double clicked on a box.
   If so, bring up a dialog for editing. Return the
   activity or nil if canceled. */
Def WM_LBUTTONDBLCLK(self, wp, lp | dPoint, activity)
{
  dPoint := windowToDisplay(self, asPoint(lp));
  activity := displayLookup(project, dPoint);
  if activity                       /* logically true if found */
    if editInfo(activity) == IDOK   /* user clicked on activity */
      dirty(self);                  /* changes were made */
      ^activity;
    endif;
  else                              /* false if nothing found */
    beep();                         /* user clicked on dead space */
  endif;
  ^nil;
}!!

/* If the right button is pressed, toggle the view
   to allow zoom in and out */
Def WM_RBUTTONDOWN(self, wp, lp)
{
  zoom(self);
}!!

/* Trap keyboard events to simulate mouse. Cursor keys move
   the cursor and scroll, return and F2 edit an activity.
   Note anything defined as an accelerator will NOT cause
   a WM_KEYDOWN message.  The keys W, X, A, D can also be 
   used to move the cursor. */
Def WM_KEYDOWN(self, wp, lp | pos, x, y, activity, max, dict)
{ 
  pos := getCursorPos(self);    /* client coords */
  x := x(pos);
  y := y(pos);

  select
    case wp == VK_UP or wp == asInt('W')
      y := y - boxHeight*2;
    endCase
    case wp == VK_DOWN or wp == asInt('X')
      y := y + boxHeight*2;
    endCase
    case wp == VK_LEFT or wp == asInt('A')
      x := x - activWidth;
    endCase
    case wp == VK_RIGHT or wp == asInt('D')
      x := x + activWidth;
    endCase
    case wp == VK_RETURN or wp == VK_F2  
      WM_LBUTTONDBLCLK(self, 1, asLong(pos));
    endCase    
  endSelect;
  
 /* If the cursor is moving off screen, scroll. */
  
  select
    case y < 0 
      y := boxHeight;
      WM_VSCROLL(self, SB_LINEUP, 1);
    endCase
    case y > max:=maxVisRow*boxHeight*2
      y := max - boxHeight;
      WM_VSCROLL(self, SB_LINEDOWN, 1);
    endCase
  endSelect;
  
  select
    case x < 0 
      x := boxHSpace;
      WM_HSCROLL(self, SB_LINEUP, 1);
    endCase
    case x > max:=maxVisCol*activWidth
      x := max - boxWidth;
      WM_HSCROLL(self, SB_LINEDOWN, 1);
    endCase
  endSelect;

  setCursorPos(self, point(x,y));
}!!

/* Trap scrolling.  Adjust as necesary.
   The wp is the scroll code, the lp gives posn. */
Def WM_HSCROLL(self, wp, lp | scroll)
{ 
  select
    case wp == SB_LINEUP 
       scroll := -1;
    endCase
    case wp == SB_LINEDOWN 
       scroll := 1;
    endCase
    case wp == SB_PAGEUP
       scroll := negate(maxVisCol);
    endCase;
    case wp == SB_PAGEDOWN
       scroll := maxVisCol;
    endCase;
    case wp == SB_THUMBPOSITION
       hScrollPos := 0; scroll := low(lp);
    endCase;
    default
       scroll := nil;
  endSelect;
 
  if scroll 
    hScrollPos := hScrollPos + scroll;    /* adjust */
    hScrollPos := min(PW_MAX_COLS, max(0, hScrollPos));
    Call SetScrollPos(hWnd, SB_HORZ, hScrollPos, 1);
    invalidate(self);
  endif;
}!!

/* Trap scrolling.  Adjust as necesary.
   The wp is the scroll code, the lp gives posn. */
Def WM_VSCROLL(self, wp, lp | scroll)
{ 
  select
    case wp == SB_LINEUP 
       scroll := -1;
    endCase
    case wp == SB_LINEDOWN 
       scroll := 1;
    endCase
    case wp == SB_PAGEUP
       scroll := negate(maxVisRow);
    endCase;
    case wp == SB_PAGEDOWN
       scroll := maxVisRow;
    endCase;
    case wp == SB_THUMBPOSITION
       vScrollPos := 0; scroll := low(lp);
    endCase;
  endSelect;
 
  if scroll
    vScrollPos := vScrollPos + scroll;    /* adjust */
    vScrollPos := min(PW_MAX_ROWS, max(0, vScrollPos));
    Call SetScrollPos(hWnd, SB_VERT, vScrollPos, 1);
    invalidate(self);
  endif;
}!!


/* Begin dragging to allow the user to draw a line
   that will connect two boxes.  Setup the anchorPt and
   dragDC to allow dragging to do the drawing.
   BW 02/05/89 */
Def beginDrag(self, wp, aPt | dPoint, activity, dMode)
{
  dPoint := windowToDisplay(self, aPt);
  activity := displayLookup(project, dPoint);
  if activity   /* logically true if found */
    anchorPt := aPt;
    dragDC := getContext(self);
    dMode := Call SetROP2(dragDC,7);
    Call SelectObject(dragDC,Call GetStockObject(6));
  endif;
}!!

/* Do the drag and draw the feedback.
   BW 02/05/89 */
Def drag(self, wp, aPt )
{ if dragDC then
    if oldPt cand (oldPt <> aPt)
      then moveTo(anchorPt,dragDC);
        lineTo(oldPt,dragDC);
    endif;
    oldPt := aPt;
    moveTo(anchorPt,dragDC);
    lineTo(aPt,dragDC);
  endif;
}!!

/* Finish the drag connection of two tasks.
   Create a new task if necessary. */
Def endDrag(self, wp, aPt | dPoint1, dPoint2, activity1, activity2)
{
  if dragDC then
    dPoint2 := windowToDisplay(self, aPt);
    activity2 := displayLookup(project, dPoint2);
    if activity2  /* logically true if found */
      dPoint1 := windowToDisplay(self,anchorPt);
      activity1 := displayLookup(project, dPoint1);
      if activity1 <> activity2
        connect(project,getName(activity1),
          getName(activity2));
        invalidate(self);
      endif;
    else /* maybe the user wants a new node */
      if activity2 := newActivity(self,Task) then
        dPoint1 := windowToDisplay(self,anchorPt);
        activity1 := displayLookup(project, dPoint1);
        connect(project,getName(activity1),
          getName(activity2));
        invalidate(self);
      else
        moveTo(anchorPt,dragDC); /* erase last line*/
        lineTo(oldPt,dragDC);
      endif;
    endif;
    releaseContext(self,dragDC); /* done drawing */
    dragDC := nil;
    oldPt := nil;
  endif;
}!!

