TweetFollow Us on Twitter

MACINTOSH C

Demonstration Program 1

Go to Contents Go to Part 2
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// Text1.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
//
// This program demonstrates:
//
// „  A "bare-bones" monostyled text editor.
//
// „  A Help dialog which features the integrated scrolling of multistyled text and 
//    pictures.
//
// In the monostyled text editor demonstration, a panel is displayed at the bottom of all
// opened windows.  This panel displays the edit record length, number of lines, line 
// height, destination rectangle (top), scroll bar value, and scroll bar maximum value. 
//
// The bulk of the source code for the Help dialog is contained in the file HelpDialog.c.
// The dialog itself displays information intended to assist the user in adapting the
// Help dialog source code and resources to the requirements of his/her own application.
//
// The program utilises the following resources:
//
// „  An 'MBAR' resource, and 'MENU' resources for Apple, File, Edit, and Help dialog
//    pop-up menus (preload, non-purgeable).  
//
// „  A 'WIND' resource (purgeable) (initially visible).  
//
// „  'CNTL' resources (purgeable) for the vertical scroll bars in the text editor window
//    and Help dialog, and for the pop-up menu in the Help Dialog.
//     
// „  A 'DLOG' resource (purgeable, initially invisible) and associated 'dctb' resource 
//    (purgeable) for the Help dialog.
//
// „  'DITL' resources (purgeable) for the 'ALRT' and 'DLOG' resources.
//
// „  'TEXT' and associated 'styl' resources (all purgeable) for the Help dialog.
//
// „  'PICT' resources (purgeable) for the Help dialog.
//
// „  A 'STR ' resource  (purgeable) containing error text strings.  
//
// „  A 'SIZE' resource with the acceptSuspendResumeEvents, doesActivateOnFGSwitch, and
//    is32BitCompatible  flags set.    
//
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

// ............................................................................. includes

#include <Appearance.h>
#include <Balloons.h>  
#include <ControlDefinitions.h>
#include <Devices.h>
#include <Gestalt.h>
#include <LowMem.h>
#include <Scrap.h>
#include <Sound.h>
#include <StandardFile.h>
#include <ToolUtils.h>

// .............................................................................. defines

#define rMenubar         128
#define mApple           128
#define  iAbout          1
#define mFile            129
#define  iNew            1
#define  iOpen           2
#define  iClose          4
#define  iSaveAs         6
#define  iQuit           12
#define mEdit            130
#define  iUndo           1
#define  iCut            3
#define  iCopy           4
#define  iPaste          5
#define  iClear          6
#define  iSelectAll      7
#define rWindow          128
#define rVScrollbar      128
#define rErrorStrings    128
#define  eMenuBar        1
#define  eMenu           2
#define  eWindow         3
#define  eDocStructure   4
#define  eEditRecord     5
#define  eExceedChara    6
#define  eNoSpaceCut     7
#define  eNoSpacePaste   8
#define kMaxTELength     32767
#define kTab             0x09
#define kDel             0x7F
#define kReturn          0x0D
#define topLeft(r)      (((Point *) &(r))[0])
#define botRight(r)      (((Point *) &(r))[1])

// ............................................................................. typedefs

typedef struct
{
  TEHandle      editRecHdl;
  ControlHandle vScrollbarHdl;
} docStructure, **docStructureHandle;

// ..................................................................... global variables

ControlActionUPP   scrollActionFunctionUPP;
TEClickLoopUPP     customClickLoopUPP;
Boolean            gMacOS85Present = false;
Boolean            gDone;
Boolean            gInBackground;
RgnHandle          gCursorRegion;
SInt16             gNumberOfWindows = 0;
SInt16             gOldControlValue;

// .................................................................. function prototypes

void            main                   (void);
void            doInitManagers         (void);
void            eventLoop              (void);
void            doIdle                 (void);
void            doEvents               (EventRecord *);
void            doKeyEvent             (SInt8);
pascal void     scrollActionFunction   (ControlHandle,SInt16);
void            doInContent            (EventRecord *);
void            doUpdate               (EventRecord *);
void            doActivate             (EventRecord *);
void            doActivateDocWindow    (WindowPtr,Boolean);
void            doOSEvent              (EventRecord *);
WindowPtr       doNewDocWindow         (void);
pascal Boolean  customClickLoop        (void);
void            doSetScrollBarValue    (ControlHandle,SInt16 *);
void            doAdjustMenus          (void);
void            doMenuChoice           (SInt32);
void            doFileMenu             (SInt16);
void            doEditMenu             (SInt16);
SInt16          doGetSelectLength      (TEHandle);
void            doAdjustScrollbar      (WindowPtr);
void            doAdjustCursor         (WindowPtr);
void            doCloseWindow          (WindowPtr);
void            doSaveAsFile           (TEHandle);
void            doOpenCommand          (void);
void            doOpenFile             (FSSpec);
void            doDrawDataPanel        (WindowPtr);
void            doErrorAlert           (SInt16);
void            doHelpMenu             (SInt16);

extern void      doHelp                (void);

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× main

void  main(void)
{
  OSErr        osError;
  SInt32       response;
  Handle       menubarHdl;
  MenuHandle   menuHdl;
  OSErr        osErr;  
  
  // .................................................................initialise managers

  doInitManagers();

  // ....................................... check whether Mac OS 8.5 or later is present

  osError = Gestalt(gestaltSystemVersion,&response);
  
  if(osError == noErr && response >= 0x00000850)
    gMacOS85Present = true;

  // ......................................................... create routine descriptors

  scrollActionFunctionUPP = NewControlActionProc((ProcPtr) scrollActionFunction);
  customClickLoopUPP = NewTEClickLoopProc((ProcPtr) customClickLoop);

  // .......................................................... set up menu bar and menus
  
  menubarHdl = GetNewMBar(rMenubar);
  if(menubarHdl == NULL)
    doErrorAlert(eMenuBar);
  SetMenuBar(menubarHdl);
  DrawMenuBar();

  menuHdl = GetMenuHandle(mApple);
  if(menuHdl == NULL)
    doErrorAlert(eMenu);
  else
    AppendResMenu(menuHdl,'DRVR');

  osErr = HMGetHelpMenuHandle(&menuHdl);
  if(osErr == noErr)
    AppendMenu(menuHdl,"\pText1 Help");
  else
    doErrorAlert(eMenu);

  // ............................................................ open an untitled window

  doNewDocWindow();

  // .................................................................... enter eventLoop

  eventLoop();
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doInitManagers

void  doInitManagers(void)
{
  MaxApplZone();
  MoreMasters();

  InitGraf(&qd.thePort);
  InitFonts();
  InitWindows();
  InitMenus();
  TEInit();
  InitDialogs(NULL);
  InitCursor();

  FlushEvents(everyEvent,0);
  
  RegisterAppearanceClient();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× eventLoop

void  eventLoop(void)
{
  EventRecord  eventStructure;
  Boolean      gotEvent;
  SInt32       sleepTime;

  gDone = false;
  gCursorRegion = NewRgn();
  sleepTime = LMGetCaretTime();

  while(!gDone)
  {
    gotEvent = WaitNextEvent(everyEvent,&eventStructure,sleepTime,gCursorRegion);

    if(gotEvent)
      doEvents(&eventStructure);
    else
    {
      if(gNumberOfWindows > 0)
        doIdle();
    }
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doIdle

void  doIdle(void)
{
  docStructureHandle  docStrucHdl;
  WindowPtr           windowPtr;
  
  windowPtr = FrontWindow();

  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  if(docStrucHdl != NULL)
    TEIdle((*docStrucHdl)->editRecHdl);
}  

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doEvents

void  doEvents(EventRecord *eventStrucPtr)
{
  WindowPtr  windowPtr;
  SInt16     partCode;
  SInt8      charCode;

  switch(eventStrucPtr->what)
  {
    case mouseDown:
      partCode = FindWindow(eventStrucPtr->where,&windowPtr);
      switch(partCode)
      {
        case inMenuBar:
          doAdjustMenus();
          doMenuChoice(MenuSelect(eventStrucPtr->where));
          break;

        case inContent:
          if(windowPtr != FrontWindow())
            SelectWindow(windowPtr);
          else
            doInContent(eventStrucPtr);
          break;

        case inDrag:
          DragWindow(windowPtr,eventStrucPtr->where,&qd.screenBits.bounds);
          doAdjustCursor(windowPtr);
          break;

        case inGoAway:
          if(TrackGoAway(windowPtr,eventStrucPtr->where))
            doCloseWindow(FrontWindow());
          break;
      }
      break;

    case keyDown:
    case autoKey:
      charCode = eventStrucPtr->message & charCodeMask;
      if((eventStrucPtr->modifiers & cmdKey) != 0)
      {
        doAdjustMenus();
        doMenuChoice(MenuEvent(eventStrucPtr));
      }
      else
        doKeyEvent(charCode);
      break;

    case updateEvt:
      doUpdate(eventStrucPtr);
      break;

    case activateEvt:
      doActivate(eventStrucPtr);
      break;

    case osEvt:
      doOSEvent(eventStrucPtr);
      break;
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doKeyEvent

void  doKeyEvent(SInt8 charCode)
{
  WindowPtr           windowPtr;
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  SInt16              selectionLength;

  windowPtr = FrontWindow();
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  if(charCode == kTab)
  {
    // Do tab key handling here if required.
  }
  else if(charCode == kDel)
  {
    selectionLength = doGetSelectLength(editRecHdl);
    if(selectionLength == 0)
      (*editRecHdl)->selEnd += 1;
    TEDelete(editRecHdl);
    doAdjustScrollbar(windowPtr);
  }
  else
  {
    selectionLength = doGetSelectLength(editRecHdl);
    if(((*editRecHdl)->teLength - selectionLength + 1) < kMaxTELength)
    {
      TEKey(charCode,editRecHdl);
      doAdjustScrollbar(windowPtr);
    }
    else
      doErrorAlert(eExceedChara);
  }

  doDrawDataPanel(windowPtr);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× scrollActionFunction

pascal void  scrollActionFunction(ControlHandle controlHdl,SInt16 partCode)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  SInt16               linesToScroll;
  SInt16               controlValue, controlMax;

  windowPtr = (*controlHdl)->contrlOwner;
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  controlValue = GetControlValue(controlHdl);
  controlMax = GetControlMaximum(controlHdl);

  if(partCode)
  {
    if(partCode != kControlIndicatorPart)
    {
      switch(partCode)
      {
        case kControlUpButtonPart:
        case kControlDownButtonPart:
          linesToScroll = 1;
          break;
        
        case kControlPageUpPart:
        case kControlPageDownPart:
          linesToScroll = (((*editRecHdl)->viewRect.bottom - (*editRecHdl)->viewRect.top) /
                           (*editRecHdl)->lineHeight) - 1;
          break;
      }

      if((partCode == kControlDownButtonPart) || (partCode == kControlPageDownPart))
        linesToScroll = -linesToScroll;

      linesToScroll = controlValue - linesToScroll;
      if(linesToScroll < 0)
        linesToScroll = 0;
      else if(linesToScroll > controlMax)
        linesToScroll = controlMax;

      SetControlValue(controlHdl,linesToScroll);

      linesToScroll = controlValue - linesToScroll;
    }
    else
    {
      linesToScroll = gOldControlValue - controlValue;
      gOldControlValue = controlValue;
    }

    if(linesToScroll != 0)
    {
      TEScroll(0,linesToScroll * (*editRecHdl)->lineHeight,editRecHdl);
      doDrawDataPanel(windowPtr);
    }
  }
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doInContent

void  doInContent(EventRecord *eventStrucPtr)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  Point                mouseXY;
  ControlHandle        controlHdl;
  SInt16               partCode;
  Boolean              shiftKeyPosition = false;

  windowPtr = FrontWindow();
  docStrucHdl = (docStructureHandle) GetWRefCon(windowPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;

  mouseXY = eventStrucPtr->where;
  SetPort(windowPtr);
  GlobalToLocal(&mouseXY);

  if((partCode = FindControl(mouseXY,windowPtr,&controlHdl)) != 0)
  {
    gOldControlValue = GetControlValue(controlHdl);
    TrackControl(controlHdl,mouseXY,scrollActionFunctionUPP);
  }
  else if(PtInRect(mouseXY,&(*editRecHdl)->viewRect))
  {
    if((eventStrucPtr->modifiers & shiftKey) != 0)
      shiftKeyPosition = true;
    TEClick(mouseXY,shiftKeyPosition,editRecHdl);
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doUpdate

void  doUpdate(EventRecord *eventStrucPtr)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  GrafPtr              oldPort;

  windowPtr = (WindowPtr) eventStrucPtr->message;
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;
  
  GetPort(&oldPort);
  SetPort(windowPtr);

  BeginUpdate((WindowPtr)eventStrucPtr->message);

  TEUpdate(&windowPtr->portRect,editRecHdl);
  UpdateControls(windowPtr,windowPtr->visRgn);

  doDrawDataPanel(windowPtr);

  EndUpdate((WindowPtr)eventStrucPtr->message);

  SetPort(oldPort);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doActivate

void  doActivate(EventRecord *eventStrucPtr)
{
  WindowPtr  windowPtr;
  Boolean    becomingActive;

  windowPtr = (WindowPtr) eventStrucPtr->message;
  becomingActive = ((eventStrucPtr->modifiers & activeFlag) == activeFlag);
  doActivateDocWindow(windowPtr,becomingActive);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doActivateDocWindow

void  doActivateDocWindow(WindowPtr windowPtr,Boolean becomingActive)
{
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;

  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  if(becomingActive)
  {
    SetPort(windowPtr);

    (*editRecHdl)->viewRect.bottom = ((((*editRecHdl)->viewRect.bottom -
                                        (*editRecHdl)->viewRect.top) / 
                                        (*editRecHdl)->lineHeight) * 
                                        (*editRecHdl)->lineHeight) + 
                                        (*editRecHdl)->viewRect.top;
    (*editRecHdl)->destRect.bottom = (*editRecHdl)->viewRect.bottom;

    TEActivate(editRecHdl);
    ActivateControl((*docStrucHdl)->vScrollbarHdl);
    doAdjustScrollbar(windowPtr);
  }
  else
  {
    TEDeactivate(editRecHdl);
    DeactivateControl((*docStrucHdl)->vScrollbarHdl);
  }
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doOSEvent

void  doOSEvent(EventRecord *eventStrucPtr)
{
  switch((eventStrucPtr->message >> 24) & 0x000000FF)
  {
    case suspendResumeMessage:
      gInBackground = (eventStrucPtr->message & resumeFlag) == 0;
      if(!gInBackground)
        SetCursor(&qd.arrow);
      if(gNumberOfWindows > 0)
        doActivateDocWindow(FrontWindow(),!gInBackground);
      HiliteMenu(0);
      break;

    case mouseMovedMessage:
      doAdjustCursor(FrontWindow());
      break;
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doNewDocWindow

WindowPtr  doNewDocWindow(void)
{
  WindowPtr           windowPtr;
  docStructureHandle  docStrucHdl;
  Rect                destAndViewRect;

  if(!(windowPtr = GetNewCWindow(rWindow,NULL,(WindowPtr) -1)))
  {
    doErrorAlert(eWindow);
    return(NULL);
  }

  SetPort(windowPtr);
  TextSize(10);

  if(!(docStrucHdl = (docStructureHandle) NewHandle(sizeof(docStructure))))
  {
    doErrorAlert(eDocStructure);
    return(NULL);
  }
  SetWRefCon(windowPtr,(SInt32) docStrucHdl);

  gNumberOfWindows ++;

  (*docStrucHdl)->vScrollbarHdl = GetNewControl(rVScrollbar,windowPtr);
  
  destAndViewRect = windowPtr->portRect;
  destAndViewRect.right -= 15;
  destAndViewRect.bottom -= 15;
  InsetRect(&destAndViewRect,2,2);

  MoveHHi((Handle) docStrucHdl);
  HLock((Handle) docStrucHdl);

  if(!((*docStrucHdl)->editRecHdl = TENew(&destAndViewRect,&destAndViewRect)))
  {
    DisposeWindow(windowPtr);
    gNumberOfWindows --;
    DisposeHandle((Handle) docStrucHdl);
    doErrorAlert(eEditRecord);
    return(NULL);
  }
  
  HUnlock((Handle) docStrucHdl);
  
  TESetClickLoop(customClickLoopUPP,(*docStrucHdl)->editRecHdl);
  TEAutoView(true,(*docStrucHdl)->editRecHdl);
  TEFeatureFlag(teFOutlineHilite,1,(*docStrucHdl)->editRecHdl);

  return(windowPtr);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× customClickLoop

pascal Boolean  customClickLoop(void)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  GrafPtr              oldPort;
  RgnHandle            oldClip;
  Rect                 tempRect;
  Point                mouseXY;
  SInt16               linesToScroll = 0;

  windowPtr = FrontWindow();
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  GetPort(&oldPort);
  SetPort(windowPtr);
  oldClip = NewRgn();
  GetClip(oldClip);
  SetRect(&tempRect,-32767,-32767,32767,32767);
  ClipRect(&tempRect);

  GetMouse(&mouseXY);
  if(mouseXY.v < windowPtr->portRect.top)
  {
    linesToScroll = 1;
    doSetScrollBarValue((*docStrucHdl)->vScrollbarHdl,&linesToScroll);
    if(linesToScroll != 0)
      TEScroll(0,linesToScroll * ((*editRecHdl)->lineHeight),editRecHdl);
  }
  else if(mouseXY.v > windowPtr->portRect.bottom)
  {
    linesToScroll = -1;
    doSetScrollBarValue((*docStrucHdl)->vScrollbarHdl,&linesToScroll);
    if(linesToScroll != 0)
      TEScroll(0,linesToScroll * ((*editRecHdl)->lineHeight),editRecHdl);
  }

  if(linesToScroll != 0)
    doDrawDataPanel(windowPtr);

  SetClip(oldClip);
  DisposeRgn(oldClip);
  SetPort(oldPort);

  return(true);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSetScrollBarValue

void  doSetScrollBarValue(ControlHandle controlHdl,SInt16 *linesToScroll)
{
  SInt16  controlValue, controlMax;

  controlValue = GetControlValue(controlHdl);
  controlMax = GetControlMaximum(controlHdl);

  *linesToScroll = controlValue - *linesToScroll;
  if(*linesToScroll < 0)
    *linesToScroll = 0;
  else if(*linesToScroll > controlMax)
    *linesToScroll = controlMax;

  SetControlValue(controlHdl,*linesToScroll);
  *linesToScroll = controlValue - *linesToScroll;
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doAdjustMenus

void  doAdjustMenus(void)
{
  MenuHandle           fileMenuHdl, editMenuHdl;
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  SInt32               scrapOffset;

  fileMenuHdl = GetMenuHandle(mFile);
  editMenuHdl = GetMenuHandle(mEdit);

  if(gNumberOfWindows > 0)
  {
    windowPtr = FrontWindow();
    docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
    editRecHdl = (*docStrucHdl)->editRecHdl;

    EnableItem(fileMenuHdl,iClose);

    if((*editRecHdl)->selStart < (*editRecHdl)->selEnd)
    {
      EnableItem(editMenuHdl,iCut);
      EnableItem(editMenuHdl,iCopy);
      EnableItem(editMenuHdl,iClear);
    }
    else
    {
      DisableItem(editMenuHdl,iCut);
      DisableItem(editMenuHdl,iCopy);
      DisableItem(editMenuHdl,iClear);
    }

    if(GetScrap(NULL,'TEXT',&scrapOffset) > 0)
      EnableItem(editMenuHdl,iPaste);
    else
      DisableItem(editMenuHdl,iPaste);
      
    if((*editRecHdl)->teLength > 0)
    {
      EnableItem(fileMenuHdl,iSaveAs);
      EnableItem(editMenuHdl,iSelectAll);
    }
    else
    {
      DisableItem(fileMenuHdl,iSaveAs);
      DisableItem(editMenuHdl,iSelectAll);
    }
  }
  else
  {
    DisableItem(fileMenuHdl,iClose);
    DisableItem(fileMenuHdl,iSaveAs);
    DisableItem(editMenuHdl,iClear);
    DisableItem(editMenuHdl,iSelectAll);
  }    

  DrawMenuBar();
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doMenuChoice

void  doMenuChoice(SInt32 menuChoice)
{
  SInt16  menuID, menuItem;
  Str255  itemName;
  SInt16  daDriverRefNum;

  menuID   = HiWord(menuChoice);
  menuItem = LoWord(menuChoice);

  if(menuID == 0)
    return;

  switch(menuID)
  {
    case mApple:
      if(menuItem == iAbout)
        SysBeep(10);
      else
      {
        GetMenuItemText(GetMenuHandle(mApple),menuItem,itemName);
        daDriverRefNum = OpenDeskAcc(itemName);
      }
      break;

    case mFile:
      doFileMenu(menuItem);
      break;

    case mEdit:
      doEditMenu(menuItem);
      break;

    case kHMHelpMenuID:
      if(FrontWindow())  
        doActivateDocWindow(FrontWindow(),false);
      doHelpMenu(menuItem);
      break;
  }

  HiliteMenu(0);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doFileMenu

void  doFileMenu(SInt16 menuItem)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;

  switch(menuItem)
  {
    case iNew:
      if(windowPtr = doNewDocWindow())
        ShowWindow(windowPtr);
      break;

    case iOpen:
      doOpenCommand();
      break;

    case iClose:
      doCloseWindow(FrontWindow());
      break;

    case iSaveAs:
      docStrucHdl = (docStructureHandle) (GetWRefCon(FrontWindow()));
      editRecHdl = (*docStrucHdl)->editRecHdl;
      doSaveAsFile(editRecHdl);
      break;

    case iQuit:
      gDone = true;
      break;
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doEditMenu

void  doEditMenu(SInt16 menuItem)
{
  WindowPtr            windowPtr;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  SInt32               totalSize, contigSize, newSize, scrapOffset;
  SInt16               selectionLength;

  windowPtr = FrontWindow();
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  switch(menuItem)
  {
    case iUndo:
      break;

    case iCut:
      if(ZeroScrap() == noErr)
      {
        PurgeSpace(&totalSize,&contigSize);
        selectionLength = doGetSelectLength(editRecHdl);
        if(selectionLength > contigSize)
          doErrorAlert(eNoSpaceCut);
        else
        {
          TECut(editRecHdl);
          doAdjustScrollbar(windowPtr);
          if(TEToScrap() != noErr)
            ZeroScrap();
        }
      }
      break;

    case iCopy:
      if(ZeroScrap() == noErr)
      {
        TECopy(editRecHdl);
        if(TEToScrap() != noErr)
          ZeroScrap();
      }
      break;

    case iPaste:
      newSize = (*editRecHdl)->teLength + GetScrap(NULL,'TEXT',&scrapOffset);
      if(newSize > kMaxTELength)
        doErrorAlert(eNoSpacePaste);
      else
      {
        if(TEFromScrap() == noErr)
        {
          TEPaste(editRecHdl);
          doAdjustScrollbar(windowPtr);
        }
      }
      break;

    case iClear:
      TEDelete(editRecHdl);
      doAdjustScrollbar(windowPtr);
      break;

    case iSelectAll:
      TESetSelect(0,(*editRecHdl)->teLength,editRecHdl);
      break;
  }

  doDrawDataPanel(windowPtr);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doGetSelectLength

SInt16  doGetSelectLength(TEHandle editRecHdl)
{
  SInt16  selectionLength;
    
  selectionLength = (*editRecHdl)->selEnd - (*editRecHdl)->selStart;
  return(selectionLength);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doAdjustScrollbar

void  doAdjustScrollbar(WindowPtr windowPtr)
{
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  SInt16              numberOfLines, controlMax, controlValue;

  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  numberOfLines = (*editRecHdl)->nLines;
  if(*(*(*editRecHdl)->hText + (*editRecHdl)->teLength - 1) == kReturn)
    numberOfLines += 1;

  controlMax = numberOfLines - (((*editRecHdl)->viewRect.bottom - 
               (*editRecHdl)->viewRect.top) /
               (*editRecHdl)->lineHeight);
  if(controlMax < 0)
    controlMax = 0;
  SetControlMaximum((*docStrucHdl)->vScrollbarHdl,controlMax);

  controlValue = ((*editRecHdl)->viewRect.top - (*editRecHdl)->destRect.top) / 
                  (*editRecHdl)->lineHeight;
  if(controlValue < 0)
    controlValue = 0;
  else if(controlValue > controlMax)
    controlValue = controlMax;

  SetControlValue((*docStrucHdl)->vScrollbarHdl,controlValue);

#if TARGET_CPU_PPC
  if(gMacOS85Present)
  {
    SetControlViewSize((*docStrucHdl)->vScrollbarHdl,(*editRecHdl)->viewRect.bottom - 
                       (*editRecHdl)->viewRect.top);
  }
#endif
  
  TEScroll(0,((*editRecHdl)->viewRect.top - (*editRecHdl)->destRect.top) - 
               (GetControlValue((*docStrucHdl)->vScrollbarHdl) *
              (*editRecHdl)->lineHeight),editRecHdl);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doAdjustCursor

void  doAdjustCursor(WindowPtr windowPtr)
{
  GrafPtr    oldPort;
  RgnHandle  arrowRegion, iBeamRegion;
  Rect       cursorRect;
  Point      mouseXY;

  if(gInBackground)
  {
    SetCursor(&qd.arrow);
    return;
  }

  GetPort(&oldPort);
  SetPort(windowPtr);

  arrowRegion = NewRgn();
  iBeamRegion = NewRgn();  
  SetRectRgn(arrowRegion,-32768,-32768,32766,32766);

  cursorRect = windowPtr->portRect;
  cursorRect.bottom -= 15;
  cursorRect.right  -= 15;
  LocalToGlobal(&topLeft(cursorRect));
  LocalToGlobal(&botRight(cursorRect));  

  RectRgn(iBeamRegion,&cursorRect);
  DiffRgn(arrowRegion,iBeamRegion,arrowRegion);
  
  GetMouse(&mouseXY);
  LocalToGlobal(&mouseXY);

  if(PtInRgn(mouseXY,iBeamRegion))
  {
    SetCursor(*(GetCursor(iBeamCursor)));
    CopyRgn(iBeamRegion,gCursorRegion);
  }
  else
  {
    SetCursor(&qd.arrow);
    CopyRgn(arrowRegion,gCursorRegion);
  }

  DisposeRgn(arrowRegion);
  DisposeRgn(iBeamRegion);

  SetPort(oldPort);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCloseWindow

void  doCloseWindow(WindowPtr windowPtr)
{
  docStructureHandle  docStrucHdl;

  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;

  DisposeControl((*docStrucHdl)->vScrollbarHdl);
  TEDispose((*docStrucHdl)->editRecHdl);
  DisposeHandle((Handle) docStrucHdl);
  DisposeWindow(windowPtr);

  gNumberOfWindows --;
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSaveAsFile

void  doSaveAsFile(TEHandle editRecHdl)
{
  StandardFileReply  fileReply;
  WindowPtr          windowPtr;
  SInt16             fileRefNum;
  SInt32             dataLength;
  Handle             editTextHdl;

  StandardPutFile("\pSave as:","\pUntitled",&fileReply);
  if(fileReply.sfGood)
  {
    windowPtr = FrontWindow();
    SetWTitle(windowPtr,fileReply.sfFile.name);

    if(!(fileReply.sfReplacing))
      FSpCreate(&fileReply.sfFile,' KJB','TEXT',fileReply.sfScript);

    FSpOpenDF(&fileReply.sfFile,fsCurPerm,&fileRefNum);

    dataLength = (*editRecHdl)->teLength;
    editTextHdl = (*editRecHdl)->hText;
    FSWrite(fileRefNum,&dataLength,*editTextHdl);

    FSClose(fileRefNum);
  }
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doOpenCommand

void  doOpenCommand(void)
{
  StandardFileReply  fileReply;
  SFTypeList         fileTypes;  

  fileTypes[0] = 'TEXT';

  StandardGetFile(NULL,1,fileTypes,&fileReply);
  if(fileReply.sfGood)
    doOpenFile(fileReply.sfFile);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doOpenFile

void  doOpenFile(FSSpec fileSpec)
{
  WindowPtr           windowPtr;
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  SInt16              fileRefNum;
  SInt32              textLength;
  Handle              textBuffer;

  if((windowPtr = doNewDocWindow()) == NULL)
    return;

  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;

  SetWTitle(windowPtr,fileSpec.name);

  FSpOpenDF(&fileSpec,fsCurPerm,&fileRefNum);

  SetFPos(fileRefNum,fsFromStart,0);
  GetEOF(fileRefNum,&textLength);

  if(textLength > 32767)
    textLength = 32767;

  textBuffer = NewHandle((Size) textLength);

  FSRead(fileRefNum,&textLength,*textBuffer);

  MoveHHi(textBuffer);
  HLock(textBuffer);

  TESetText(*textBuffer,textLength,editRecHdl);

  HUnlock(textBuffer);
  DisposeHandle(textBuffer);

  FSClose(fileRefNum);

  (*editRecHdl)->selStart = 0;
  (*editRecHdl)->selEnd = 0;

  ShowWindow(windowPtr);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDrawDataPanel

void  doDrawDataPanel(WindowPtr windowPtr)
{
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  RGBColor             whiteColour = { 0xFFFF, 0xFFFF, 0xFFFF };          
  RGBColor             blackColour = { 0x0000, 0x0000, 0x0000 };          
  RGBColor             blueColour = { 0x1818, 0x4B4B, 0x8181 };
  ControlHandle        controlHdl;
  Rect                 panelRect;
  Str255               textString;
        
  docStrucHdl = (docStructureHandle) (GetWRefCon(windowPtr));;
  editRecHdl = (*docStrucHdl)->editRecHdl;
  controlHdl = (*docStrucHdl)->vScrollbarHdl;

  MoveTo(0,282);
  LineTo(495,282);

  RGBForeColor(&whiteColour);
  RGBBackColor(&blueColour);
  SetRect(&panelRect,0,283,495,300);
  EraseRect(&panelRect);

  MoveTo(3,295);
  DrawString("\pteLength               nLines          lineHeight");

  MoveTo(225,295);
  DrawString("\pdestRect.top              controlValue           contrlMax");

  SetRect(&panelRect,47,284,88,299);
  EraseRect(&panelRect);  
  SetRect(&panelRect,124,284,149,299);
  EraseRect(&panelRect);  
  SetRect(&panelRect,204,284,222,299);
  EraseRect(&panelRect);  
  SetRect(&panelRect,286,284,323,299);
  EraseRect(&panelRect);  
  SetRect(&panelRect,389,284,416,299);
  EraseRect(&panelRect);  
  SetRect(&panelRect,472,284,495,299);
  EraseRect(&panelRect);  

  NumToString((SInt32) (*editRecHdl)->teLength,textString);
  MoveTo(47,295);
  DrawString(textString);

  NumToString((SInt32) (*editRecHdl)->nLines,textString);
  MoveTo(124,295);
  DrawString(textString);

  NumToString((SInt32) (*editRecHdl)->lineHeight,textString);
  MoveTo(204,295);
  DrawString(textString);

  NumToString((SInt32) (*editRecHdl)->destRect.top,textString);
  MoveTo(286,295);
  DrawString(textString);

  NumToString((SInt32) GetControlValue(controlHdl),textString);
  MoveTo(389,295);
  DrawString(textString);

  NumToString((SInt32) GetControlMaximum(controlHdl),textString);
  MoveTo(472,295);
  DrawString(textString);

  RGBForeColor(&blackColour);
  RGBBackColor(&whiteColour);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doErrorAlert

void  doErrorAlert(SInt16 errorCode)
{
  AlertStdAlertParamRec  paramRec;
  Str255                 errorString;
  SInt16                 itemHit;

  paramRec.movable         = true;
  paramRec.helpButton      = false;
  paramRec.filterProc      = NULL;
  paramRec.defaultText     = (StringPtr) kAlertDefaultOKText;
  paramRec.cancelText      = NULL;
  paramRec.otherText       = NULL;
  paramRec.defaultButton   = kAlertStdAlertOKButton;
  paramRec.cancelButton    = 0;
  paramRec.position        = kWindowDefaultPosition;

  GetIndString(errorString,rErrorStrings,errorCode);

  if(errorCode < eWindow)
  {
    StandardAlert(kAlertStopAlert,errorString,NULL,¶mRec,&itemHit);
    ExitToShell();
  }
  else
    StandardAlert(kAlertCautionAlert,errorString,NULL,¶mRec,&itemHit);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doHelpMenu

void  doHelpMenu(SInt16 menuItem)
{
  MenuHandle  helpMenuHdl;
  SInt16      origHelpItems, numItems;

  HMGetHelpMenuHandle(&helpMenuHdl);

  numItems = CountMenuItems(helpMenuHdl);
  origHelpItems = numItems - 1;

  if(menuItem > origHelpItems)
    doHelp();  
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// HelpDialog.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

#include <Appearance.h>
#include <ControlDefinitions.h>
#include <Dialogs.h>
#include <Resources.h>
#include <TextUtils.h>

// .............................................................................. defines

#define rHelpModal             128
#define  iUserPane             2
#define  iScrollBar            3
#define  iPopupMenu            4
#define  eHelpDialog           9
#define  eHelpDocRecord        10
#define  eHelpText             11
#define  eHelpPicture          12
#define rTextIntroduction      128
#define rTextCreatingText      129
#define rTextModifyHelp        130
#define rPictIntroductionBase  128
#define rPictCreatingTextBase  129
#define kTextInset             4

// ............................................................................. typedefs

typedef struct
{
  Rect      bounds;
  PicHandle  pictureHdl;
} pictInfoStructure;

typedef struct
{
  TEHandle           editRecHdl;
  ControlHandle      scrollbarHdl;
  SInt16             pictCount;
  pictInfoStructure  *pictInfoStructurePtr;
}  docStructure, ** docStructureHandle;

typedef struct
{
  RGBColor       backColour;
  PixPatHandle   backPixelPattern;
  Pattern        backBitPattern;
} backColourPattern;

// ..................................................................... global variables

ModalFilterUPP           eventFilterUPP;
ControlUserPaneDrawUPP   userPaneDrawFunctionUPP;
ControlActionUPP         actionFunctionUPP;
backColourPattern        gBackColourPattern;
SInt16                   gTextResourceID;
SInt16                   gPictResourceBaseID;
RgnHandle                gSavedClipRgn  = NULL;

extern Boolean          gMacOS85Present;

// .................................................................. function prototypes

void            doHelp                 (void);
void            doCloseHelp            (DialogPtr,GrafPtr);
void            doDisposeDescriptors   (void);
pascal void     userPaneDrawFunction   (ControlHandle,SInt16);
Boolean         doGetText              (DialogPtr,SInt16,Rect);
Boolean         doGetPictureInfo       (DialogPtr,SInt16);
pascal void     actionFunction         (ControlHandle,SInt16);
void            doScrollTextAndPicts   (DialogPtr);
void            doDrawPictures         (DialogPtr,Rect *);
pascal Boolean  eventFilter            (DialogPtr,EventRecord *,SInt16 *);
void            doSaveBackground       (backColourPattern *);
void            doRestoreBackground    (backColourPattern *);
void            doSetBackgroundWhite   (void);

extern void     doUpdate               (EventRecord *);
extern void     doErrorAlert           (SInt16);

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doHelp

void  doHelp(void)
{
  DialogPtr            dialogPtr;
  docStructureHandle   docStrucHdl;
  GrafPtr              oldPort;
  ControlHandle        controlHdl;
  SInt16               itemHit, menuItem;
  Rect                 userItemRect, destRect, viewRect;

  // ......................................................... create routine descriptors

  eventFilterUPP          = NewModalFilterProc((ProcPtr) eventFilter);
  userPaneDrawFunctionUPP  = NewControlUserPaneDrawProc((ProcPtr) userPaneDrawFunction);
  actionFunctionUPP        = NewControlActionProc((ProcPtr) actionFunction);

  // ........................................ create dialog and attach document structure

  if(!(dialogPtr = GetNewDialog(rHelpModal,NULL,(WindowPtr) -1)))
  {
    doErrorAlert(eHelpDialog);
    doDisposeDescriptors();
    return;
  }

  if(!(docStrucHdl = (docStructureHandle) NewHandle(sizeof(docStructure))))
  {
    doErrorAlert(eHelpDocRecord);
    DisposeDialog(dialogPtr);
    doDisposeDescriptors();
    return;
  }

  SetWRefCon(dialogPtr,(SInt32) docStrucHdl);

  // ............................................... set graphics port and default button
  
  GetPort(&oldPort);
  SetPort(dialogPtr);
  SetDialogDefaultItem(dialogPtr,kStdOkItemIndex);

  // ..................................................... set user pane drawing function
  
  GetDialogItemAsControl(dialogPtr,iUserPane,&controlHdl);
  SetControlData(controlHdl,kControlNoPart,kControlUserPaneDrawProcTag, 
                 sizeof(userPaneDrawFunctionUPP),(Ptr) &userPaneDrawFunctionUPP);

  // ......................... set destination and view rectangles, create edit structure

  userItemRect = (*controlHdl)->contrlRect;
  InsetRect(&userItemRect,kTextInset,kTextInset / 2);
  destRect = viewRect = userItemRect;
  (*docStrucHdl)->editRecHdl = TEStyleNew(&destRect,&viewRect);

  // ................... assign handle to scroll bar to relevant document structure field 

  GetDialogItemAsControl(dialogPtr,iScrollBar,&controlHdl);
  (*docStrucHdl)->scrollbarHdl = controlHdl;

  // ............... initialise picture information structure field of document structure 

  (*docStrucHdl)->pictInfoStructurePtr = NULL;

  // ....................... assign resource IDs of first topic's 'TEXT'/'styl' resources
  
  gTextResourceID      = rTextIntroduction;
  gPictResourceBaseID  = rPictIntroductionBase;

  // ................................. load text resources and insert into edit structure

  if(!(doGetText(dialogPtr,gTextResourceID,viewRect)))
  {
    doCloseHelp(dialogPtr,oldPort);
    doDisposeDescriptors();
    return;
  }

  // .... search for option-space charas in text and load same number of 'PICT' resources

  if(!(doGetPictureInfo(dialogPtr,gPictResourceBaseID)))
  {
    doCloseHelp(dialogPtr,oldPort);
    doDisposeDescriptors();
    return;
  }

  // .......................... create an empty region for saving the old clipping region
    
  gSavedClipRgn = NewRgn();

  // ................................. show window and save background colour and pattern

  ShowWindow(dialogPtr);

  doSaveBackground(&gBackColourPattern);

  // ............................................................. enter ModalDialog loop

  do 
  {
    ModalDialog(eventFilterUPP,&itemHit);
  
    if(itemHit == iPopupMenu)
    {
      SetControlValue((*docStrucHdl)->scrollbarHdl,0);

      GetDialogItemAsControl(dialogPtr,iPopupMenu,&controlHdl);
      menuItem = GetControlValue(controlHdl);
      
      switch(menuItem)
      {
        case 1:
          gTextResourceID     = rTextIntroduction;
          gPictResourceBaseID = rPictIntroductionBase;
          break;
        
        case 2:
          gTextResourceID     = rTextCreatingText;
          gPictResourceBaseID = rPictCreatingTextBase;
          break;

        case 3:
          gTextResourceID     = rTextModifyHelp;
          break;
      }
  
      doSetBackgroundWhite();
      
      if(!(doGetText(dialogPtr,gTextResourceID,viewRect)))
      {
        doCloseHelp(dialogPtr,oldPort);
        doDisposeDescriptors();
        return;
      }
      if(!(doGetPictureInfo(dialogPtr,gPictResourceBaseID)))
      {
        doCloseHelp(dialogPtr,oldPort);
        doDisposeDescriptors();
        return;
      }

      doDrawPictures(dialogPtr,&viewRect);
      
      doRestoreBackground(&gBackColourPattern);
    }
      
  } while(itemHit != kStdOkItemIndex);

  // ........................................................................... clean up
    
  doCloseHelp(dialogPtr,oldPort);
  doDisposeDescriptors();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCloseHelp

void  doCloseHelp(DialogPtr dialogPtr,GrafPtr oldPort)
{
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  SInt16              a;
  
  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;

  if(gSavedClipRgn)
    DisposeRgn(gSavedClipRgn);

  if((*docStrucHdl)->editRecHdl)
    TEDispose((*docStrucHdl)->editRecHdl);

  if((*docStrucHdl)->pictInfoStructurePtr)
  {
    for(a=0;a<(*docStrucHdl)->pictCount;a++)
      ReleaseResource((Handle) (*docStrucHdl)->pictInfoStructurePtr[a].pictureHdl);
    DisposePtr((Ptr) (*docStrucHdl)->pictInfoStructurePtr);
  }

  DisposeHandle((Handle) docStrucHdl);
  DisposeDialog(dialogPtr);

  SetPort(oldPort);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDisposeDescriptors

void  doDisposeDescriptors(void)
{
  DisposeRoutineDescriptor(eventFilterUPP);
  DisposeRoutineDescriptor(userPaneDrawFunctionUPP);
  DisposeRoutineDescriptor(actionFunctionUPP);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× userPaneDrawFunction

pascal void  userPaneDrawFunction(ControlHandle controlHdl,SInt16 thePart)
{
  Rect                 itemRect, viewRect;
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  DialogPtr            dialogPtr;
  Boolean              inState;

  dialogPtr = (*controlHdl)->contrlOwner;
  itemRect = (*controlHdl)->contrlRect;
  InsetRect(&itemRect,1,1);
  itemRect.right += 15;

  DrawThemeListBoxFrame(&itemRect,kThemeStateActive);
  if(((WindowPeek) dialogPtr)->visible)
    inState = ((WindowPeek) dialogPtr)->hilited;
  DrawThemeListBoxFrame(&itemRect,inState);

  doSetBackgroundWhite();
  EraseRect(&itemRect);
  
  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;
  viewRect = (*editRecHdl)->viewRect;

  TEUpdate(&viewRect,editRecHdl);
  doDrawPictures(dialogPtr,&viewRect);

  doRestoreBackground(&gBackColourPattern);  
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doGetText

Boolean  doGetText(DialogPtr dialogPtr,SInt16 textResourceID,Rect viewRect)
{
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  Handle              helpTextHdl;
  StScrpHandle        stylScrpRecHdl;
  SInt16              numberOfLines, heightOfText, heightToScroll;

  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;

  TESetSelect(0,32767,editRecHdl);
  TEDelete(editRecHdl);
  
  (*editRecHdl)->destRect = (*editRecHdl)->viewRect;
  SetControlValue((*docStrucHdl)->scrollbarHdl,0);
      
  helpTextHdl = GetResource('TEXT',textResourceID);
  if(helpTextHdl == NULL)
  {
    doErrorAlert(eHelpText);
    return false;
  }

  stylScrpRecHdl = (StScrpHandle) GetResource('styl',textResourceID);
  if(stylScrpRecHdl == NULL)
  {
    doErrorAlert(eHelpText);
    return false;
  }
    
  TEStyleInsert(*helpTextHdl,GetHandleSize(helpTextHdl),stylScrpRecHdl,editRecHdl);

  ReleaseResource(helpTextHdl);
  ReleaseResource((Handle) stylScrpRecHdl);

  numberOfLines = (*editRecHdl)->nLines;
  heightOfText = TEGetHeight((SInt32) numberOfLines,1,editRecHdl);

  if(heightOfText > (viewRect.bottom - viewRect.top))
  {
    heightToScroll = TEGetHeight((SInt32) numberOfLines,1,editRecHdl) -
                                 (viewRect.bottom - viewRect.top);
    SetControlMaximum((*docStrucHdl)->scrollbarHdl,heightToScroll);
    ActivateControl((*docStrucHdl)->scrollbarHdl);
#if TARGET_CPU_PPC
    if(gMacOS85Present)
    {
      SetControlViewSize((*docStrucHdl)->scrollbarHdl,(*editRecHdl)->viewRect.bottom - 
                         (*editRecHdl)->viewRect.top);
    }
#endif
  }
  else
  {
    DeactivateControl((*docStrucHdl)->scrollbarHdl);
  }

  return true;
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doGetPictureInfo

Boolean  doGetPictureInfo(DialogPtr dialogPtr,SInt16 firstPictID)
{
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  Handle               textHdl;
  SInt32               offset, textSize;
  SInt16               numberOfPicts, a, lineHeight, fontAscent;
  SInt8                optionSpace[1] = "\xCA";
  pictInfoStructure    *pictInfoPtr;
  Point                picturePoint;
  TextStyle            whatStyle;
  
  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);

  if((*docStrucHdl)->pictInfoStructurePtr != NULL)
  {
    for(a=0;a<(*docStrucHdl)->pictCount;a++)
      ReleaseResource((Handle) (*docStrucHdl)->pictInfoStructurePtr[a].pictureHdl);

    DisposePtr((Ptr) (*docStrucHdl)->pictInfoStructurePtr);
    (*docStrucHdl)->pictInfoStructurePtr = NULL;
  }

  (*docStrucHdl)->pictCount = 0;

  editRecHdl = (*docStrucHdl)->editRecHdl;
  textHdl = (*editRecHdl)->hText;

  textSize = GetHandleSize(textHdl);
  offset = 0;
  numberOfPicts = 0;

  HLock(textHdl);

  offset = Munger(textHdl,offset,optionSpace,1,NULL,0);
  while((offset >= 0) && (offset <= textSize))
  {
    numberOfPicts++;
    offset++;
    offset = Munger(textHdl,offset,optionSpace,1,NULL,0);    
  }
  
  if(numberOfPicts == 0)
  {
    HUnlock(textHdl);
    return true;
  }
    
  pictInfoPtr = (pictInfoStructure *) NewPtr(sizeof(pictInfoStructure) * numberOfPicts);
  (*docStrucHdl)->pictInfoStructurePtr = pictInfoPtr;

  offset = 0L;

  for(a=0;a<numberOfPicts;a++)
  {
    pictInfoPtr[a].pictureHdl = GetPicture(firstPictID + a);
    if(pictInfoPtr[a].pictureHdl == NULL)
    {
      doErrorAlert(eHelpPicture);
      return false;
    }

    offset = Munger(textHdl,offset,optionSpace,1,NULL,0);
    picturePoint = TEGetPoint((SInt16)offset,editRecHdl);

    TEGetStyle(offset,&whatStyle,&lineHeight,&fontAscent,editRecHdl);
    picturePoint.v -= lineHeight;
    offset++;
    pictInfoPtr[a].bounds = (**pictInfoPtr[a].pictureHdl).picFrame;

    OffsetRect(&pictInfoPtr[a].bounds,
               (((*editRecHdl)->destRect.right + (*editRecHdl)->destRect.left) -
               (pictInfoPtr[a].bounds.right + pictInfoPtr[a].bounds.left) ) / 2,
               - pictInfoPtr[a].bounds.top + picturePoint.v);
  }
  
  (*docStrucHdl)->pictCount = a;
  
  HUnlock(textHdl);

  return true;
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× actionFunction

pascal void  actionFunction(ControlHandle scrollbarHdl,SInt16 partCode)
{
  docStructureHandle   docStrucHdl;
  DialogPtr            dialogPtr;
  TEHandle             editRecHdl;
  SInt16               delta, oldValue, offset, lineHeight, fontAscent;
  Point                thePoint;
  Rect                 viewRect;
  TextStyle            style;

  if(partCode)
  {
    dialogPtr = (*scrollbarHdl)->contrlOwner;
    docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
    editRecHdl = (*docStrucHdl)->editRecHdl;
    viewRect = (*editRecHdl)->viewRect;
    thePoint.h = viewRect.left + kTextInset;
    
    if(partCode != kControlIndicatorPart)
    {    
      switch(partCode)
      {
        case kControlUpButtonPart:
          thePoint.v = viewRect.top - 4;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          TEGetStyle(offset,&style,&lineHeight,&fontAscent,editRecHdl);
          delta = thePoint.v - lineHeight - viewRect.top;
          break;

        case kControlDownButtonPart:
          thePoint.v = viewRect.bottom + 2;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          delta = thePoint.v - viewRect.bottom;
          break;

        case kControlPageUpPart:
          thePoint.v = viewRect.top + 2;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          TEGetStyle(offset,&style,&lineHeight,&fontAscent,editRecHdl);
          thePoint.v += lineHeight - fontAscent;
          thePoint.v -= viewRect.bottom - viewRect.top;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          TEGetStyle(offset,&style,&lineHeight,&fontAscent,editRecHdl);
          delta = thePoint.v - viewRect.top;
          if(offset == 0)
            delta -= lineHeight;
          break;
        
        case kControlPageDownPart:
          thePoint.v = viewRect.bottom - 2;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          TEGetStyle(offset,&style,&lineHeight,&fontAscent,editRecHdl);
          thePoint.v -= fontAscent;
          thePoint.v += viewRect.bottom - viewRect.top;
          offset = TEGetOffset(thePoint,editRecHdl);
          thePoint = TEGetPoint(offset,editRecHdl);
          TEGetStyle(offset,&style,&lineHeight,&fontAscent,editRecHdl);
          delta =  thePoint.v - lineHeight - viewRect.bottom;
          if(offset == (**editRecHdl).teLength)
            delta += lineHeight;
          break;
      }

      oldValue = GetControlValue(scrollbarHdl);

      if(((delta < 0) && (oldValue > 0)) || ((delta > 0) && 
         (oldValue < GetControlMaximum(scrollbarHdl))))
      {
         GetClip(gSavedClipRgn);
        ClipRect(&dialogPtr->portRect);

        SetControlValue(scrollbarHdl,oldValue + delta);
        SetClip(gSavedClipRgn);
      }
    }
    
    doScrollTextAndPicts(dialogPtr);
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doScrollTextAndPicts

void  doScrollTextAndPicts(DialogPtr dialogPtr)
{
  docStructureHandle  docStrucHdl;
  TEHandle            editRecHdl;
  SInt16              scrollDistance, oldScroll;
  Rect                updateRect;

  doSetBackgroundWhite();

  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;

  oldScroll = (*editRecHdl)->viewRect.top -(*editRecHdl)->destRect.top;
  scrollDistance = oldScroll - GetControlValue((*docStrucHdl)->scrollbarHdl);
  if(scrollDistance == 0)
  {
    doRestoreBackground(&gBackColourPattern);  
    return;
  
  }
  TEScroll(0,scrollDistance,editRecHdl);

  if((*docStrucHdl)->pictCount == 0)
  {
    doRestoreBackground(&gBackColourPattern);  
    return;
  }
    
  updateRect = (*editRecHdl)->viewRect;

  if(scrollDistance > 0)
  {
    if(scrollDistance < (updateRect.bottom - updateRect.top))
      updateRect.bottom = updateRect.top + scrollDistance;
  }
  else
  {
    if( - scrollDistance < (updateRect.bottom - updateRect.top))
      updateRect.top = updateRect.bottom + scrollDistance;
  }

  doDrawPictures(dialogPtr,&updateRect);

  doRestoreBackground(&gBackColourPattern);  
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDrawPictures

void  doDrawPictures(DialogPtr dialogPtr,Rect *updateRect)
{
  docStructureHandle   docStrucHdl;
  TEHandle             editRecHdl;
  SInt16               pictCount, pictIndex, vOffset;
  PicHandle            thePictHdl;
  Rect                 pictLocRect, dummyRect;

  docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
  editRecHdl = (*docStrucHdl)->editRecHdl;

  vOffset = (*editRecHdl)->destRect.top - (*editRecHdl)->viewRect.top - kTextInset;
  pictCount = (*docStrucHdl)->pictCount;

  for(pictIndex = 0;pictIndex < pictCount;pictIndex++)
  {
    pictLocRect = (*docStrucHdl)->pictInfoStructurePtr[pictIndex].bounds;
    OffsetRect(&pictLocRect,0,vOffset);

    if(!SectRect(&pictLocRect,updateRect,&dummyRect))
      continue;

    thePictHdl = (*docStrucHdl)->pictInfoStructurePtr[pictIndex].pictureHdl;

    LoadResource((Handle) thePictHdl);
    HLock((Handle) thePictHdl);

    GetClip(gSavedClipRgn);
    ClipRect(updateRect);
    DrawPicture(thePictHdl,&pictLocRect);

    SetClip(gSavedClipRgn);
    HUnlock((Handle) thePictHdl);
  }
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× eventFilter

pascal Boolean  eventFilter(DialogPtr dialogPtr,EventRecord *eventStrucPtr,
                            SInt16 *itemHit)
{
  Boolean              handledEvent;
  GrafPtr              oldPort;
  Point                mouseXY;
  ControlHandle        controlHdl;
  docStructureHandle   docStrucHdl;

  handledEvent = false;

  if((eventStrucPtr->what == updateEvt) && 
     ((WindowPtr) eventStrucPtr->message != dialogPtr))
  {
    doUpdate(eventStrucPtr);
  }
  else
  {
    GetPort(&oldPort);
    SetPort(dialogPtr);

    if(eventStrucPtr->what == mouseDown)
    {
      mouseXY = eventStrucPtr->where;
      GlobalToLocal(&mouseXY);
      if(FindControl(mouseXY,dialogPtr,&controlHdl))
      {
        docStrucHdl = (docStructureHandle) GetWRefCon(dialogPtr);
        if(controlHdl == (*docStrucHdl)->scrollbarHdl)
        {
          TrackControl((*docStrucHdl)->scrollbarHdl,mouseXY,actionFunctionUPP);  
          *itemHit = iScrollBar;
          handledEvent = true;
        }
      }
    }
    else
    {
      handledEvent = StdFilterProc(dialogPtr,eventStrucPtr,itemHit);
    }

    SetPort(oldPort);
  }

  return(handledEvent);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSaveBackground

void  doSaveBackground(backColourPattern *gBackColourPattern)
{
  GrafPtr  currentPort;
  
  GetPort(¤tPort);

  GetBackColor(&gBackColourPattern->backColour);
  gBackColourPattern->backPixelPattern  = NULL;

  if((**((CGrafPtr) currentPort)->bkPixPat).patType != 0)
    gBackColourPattern->backPixelPattern = ((CGrafPtr) currentPort)->bkPixPat;
  else
    gBackColourPattern->backBitPattern = 
                             *(PatPtr) (*(**((CGrafPtr) currentPort)->bkPixPat).patData);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doRestoreBackground

void  doRestoreBackground(backColourPattern *gBackColourPattern)
{
  RGBBackColor(&gBackColourPattern->backColour);

  if(gBackColourPattern->backPixelPattern)
    BackPixPat(gBackColourPattern->backPixelPattern);
  else
    BackPat(&gBackColourPattern->backBitPattern);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSetBackgroundWhite

void  doSetBackgroundWhite(void)
{
  RGBColor  whiteColour = { 0xFFFF, 0xFFFF, 0xFFFF };

  RGBBackColor(&whiteColour);
  BackPat(&qd.white);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

Demonstration Program 1 Comments

When this program is run, the user should explore both the text editor and the Help
dialog.

Text Editor

In the text editor, the user should perform all the actions usually associated with a
simple text editor, that is:

*   Open a new document window, open an existing 'TEXT' file for display in a new 
    document window, and save a document to a 'TEXT' file.

*   Enter new text and use the Edit menu Cut, Copy, Paste, and Clear commands to edit
    the text.  (Pasting between documents and other applications is supported.)

*   Select text by clicking and dragging, double-clicking a word, shift-clicking, and 
    choosing the Select All command from the Edit menu.  Also select large amounts of
    text by clicking in the text and dragging the cursor above or below the window so as
    to invoke auto-scrolling.

*   Scroll a large document by dragging the scroll box (live scrolling is used), 
    clicking once in a scroll arrow or gray area, and holding the mouse down in a scroll
    arrow or gray area.

Whenever any action is taken, the user should observe the changes to the values displayed
in the data panel at the bottom of each window.  In particular, the relationship between
the destination rectangle and scroll bar control value should be noted.

The user should also note that outline highlighting is activated for all windows and that
the forward-delete key is supported by the application.  (The forward-delete key is not
supported by TextEdit.)

Help Dialog

The user should choose Text1 Help from the Help menu to open the Help dialog and then
scroll through the three help topics, which may be chosen in the pop-up menu at the
bottom of the dialog.  The help topics contain documentation on the Help dialog which
supplements the source code comments below.

Text1.c

#define

kMaxTELength represents the maximum allowable number of bytes in a TextEdit edit
structure.  kTab, kDel, and kReturn representing the character codes generated by the
tab, delete and return keys.

#typedef

The docStructure data type will be used for a small document structure comprising a
handle to an edit structure and a handle to a (vertical) scroll bar.

Global Variables

scrollActionFunctionUPP will be assigned a universal procedure pointer to an action 
function for the scroll bar.  customClickLoopUPP will be assigned a universal procedure 
pointer to a custom click loop function.  gMacOS85Present will be assigned true if Mac OS 
8.5 or later is present.  gCursorRegion is related to the WaitNextEvent's mouseRgn 
parameter.  gNumberOfWindows will keep track of the number of windows open at any one time.
gOldControlValue will be assigned the scroll bar's control value.

main

The main function initialises the system software managers, sets up the menus, opens 
a new document window and enters the event loop.  gMacOS85Present is assigned true of Mac 
OS 8.5 or later is present.  This global variable will govern whether certain Control 
Manager functions relating to proportional scroll boxes, and which are available only in 
Mac OS 8.5 or later, are called.

eventLoop

Note that WaitNextEvent's sleep parameter is assigned the value returned by the call to
LMGetCaretTime.

If WaitNextEvent returns a null event, and provided at least one window is open, the
application-defined function doIdle is called.

doIdle

doIdle is called whenever a NULL event is received.

The first line gets a pointer to the front window, allowing the next line to attempt to
retrieve a handle to that window's document structure.  If the attempt is successful,
TEIdle is called to blink the insertion point.

doEvents

Note that, in the case of a mouse-down event in the content area, the application-defined
function doInContent is called and that, in the case of key events, the
application-defined function doKeyEvent is called provided the key press is not a Command
key equivalent.

doKeyEvent

doKeyEvent handles all key-down events that are not Command key equivalents.  

The first three lines get a handle to the edit structure which forms part of the front
window's document structure.

The next line filters out the tab key character code.  (TextEdit does not support the tab
key and some applications may need to provide a tab key handler.)

The next character code to be filtered out is the forward-delete key character code. 
TextEdit does not recognise this key, so this else if block provides forward-delete key
support for the program.  The first line in this block gets the current selection length
from the edit structure.  If this is zero (that is, there is no selection range and an
insertion point is being displayed), the selEnd field is increased by one.  This, in
effect, creates a selection range comprising the character following the insertion point. 
TEDelete deletes the current selection range from the edit structure.  Such deletions
could change the number of text lines in the edit structure, requiring the vertical
scroll bar to be adjusted; hence the call to the application-defined function
doAdjustScrollbar.

Processing of those character codes which have not been filtered out is performed in the
else block.  A new character must not be allowed to be inserted in the edit structure if
the TextEdit limit of 32,767 characters will be exceeded.  Accordingly, and given that
TEKey replaces the selection range with the character passed to it, the first step is to
get the current selection length.  If the current length of the edit structure minus the
selection length plus 1 is less than 32,767, the character code is passed to TEKey for
insertion into the edit structure.  In addition, and since all this could change the
number of lines in the edit structure, the scroll bar adjustment function is called.

If the TextEdit limit will be exceeded by accepting the character, an alert box is
invoked advising the user of the situation.

The last line calls the application-defined function which prints data extracted from the
edit text and control structures at the bottom of the window.

scrollActionFunction

scrollActionFunction is associated with the vertical scroll bar.  It is the hook function
which will be repeatedly called by TrackControl while the mouse button remains down in
the scroll box, scroll arrows or gray areas of the vertical scroll bar.

The first line gets a pointer to the window which "owns" the control.  The next two lines
get a handle to the edit structure associated with the window.

Within the outer if block, the first if block executes if the control part is not the 
scroll box (indicator).  The purpose of the switch is to get a value into the variable 
linesToScroll.  If the mouse-down was in a scroll arrow, that value will be 1.  If the 
mouse-down was in a gray area, that value will be equivalent to one less than the number 
of text lines that will fit in the view rectangle.  (Subtracting 1 from the total number 
of lines that will fit in the view rectangle ensures that the line of text at the 
bottom/top of the view rectangle prior to a gray area scroll will be visible at the 
top/bottom of the window after the scroll.)

Immediately after the switch, the value in linesToScroll is changed to a negative value
if the mouse-down occurred in either the down scroll arrow or down gray area.

The next block ensures that no scrolling action will occur if the document is currently
scrolled fully up (control value equals control maximum) or fully down (control value
equals 0).  In either case, linesToScroll will be set to 0, meaning that the call to
TEScroll near the end of the function will not occur.

SetControlValue sets the control value to the value just previously calculated, that is,
to the current control value minus the value in linesToScroll.

The next line sets the value in linesToScroll back to what it was before the line
linesToScroll = controlvalue - linesToScroll executed. This value, multiplied by the
value in the lineHeight field of the edit structure, is later passed to TEScroll as the
parameter which specifies the number of pixels to scroll.

If the control part is the scroll box (indicator), the variable linesToScroll is assigned
a value equal to the control's value as it was last time this function was called minus
the control's current value.  The global variable which holds the control's "old" value
is then assigned the control's current value preparatory to the next call to this
function.

With the number of lines to scroll determined, TEScroll is called to scroll the text
within the view rectangle by the number of pixels specified in the second parameter. 
(Positive values scroll the text towards the bottom of the screen.  Negative values
scroll the text towards the top.)

The last line is for demonstration purposes only.  It calls the application-defined
function which prints data extracted from the edit and control structures at the bottom
of the window.

doInContent

doInContent continues mouse-down processing.

The first three lines retrieve a handle to the edit structure associated with that
window.

The next two lines convert the mouse-down coordinates from global to local coordinates. 
(Local coordinates will be required by upcoming calls to FindControl, TrackControl, and
PtInRect.)

If the mouse-down was in a control (that is, the scroll bar), the global variable
gOldControlValue is assigned the value of the control to cater for the case of the
mouse-down being within the scroll box (indicator), and TrackControl is called with the
universal procedure pointer to the application defined action function passed in the
third parameter.  TrackControl retains control until the mouse button is released, during
which time the previously described action function scrollActionFunction is repeatedly
called.

If the mouse-down was not in a control, PtInRect checks whether it occurred within the
view rectangle.  (Note that the view rectangle is in local coordinates, so the mouse-down
coordinates passed as the first parameter to the PtInRect call must also be in local
coordinates.)  If the mouse-down was in the view rectangle, a check is made of the shift
key position at the time of the mouse-down.  The result is passed as the second parameter
in the call to TEClick.  (TEClick's behaviour depends on the position of the shift key.)

doUpdate

doUpdate handles update events.  The first three lines get the handle to the edit
structure associated with the window.

Between the usual BeginUpdate and EndUpdate calls, TEUpdate is called to draw the text in
the edit structure, UpdateControls is called to draw the scroll bar, and the data panel
is redrawn.

doActivateDocWindow

doActivateDocWindow handles window activation/deactivation.

The first two lines retrieve a handle to the edit structure for the window.

If the window is becoming active, its graphics port is set as the current graphics port. 
The bottom of the view rectangle is then adjusted so that the height of the view
rectangle is an exact multiple of the value in the lineHeight field of the edit
structure.  (This avoids the possibility of only part of the full height of a line of
text appearing at the bottom of the view rectangle.)  TEActivate activates the edit
structure associated with the window, ActivateControl activates the scroll bar, and
doAdjustScrollbar adjusts the scroll bar.

If the window is becoming inactive, TEDeactivate deactivates the edit structure
associated with the window and DeactivateControl deactivates the scroll bar.
doNewDocWindow

doNewDocWindow is called at program launch and when the user chooses New or Open from the
File menu.  It opens a new window, attaches a document structure to that window, creates
a vertical scroll bar, creates a monostyled edit structure, installs the custom click
loop function, enables automatic scrolling, and enables outline highlighting.

GetNewCWindow opens a new window and sets its colour graphics port as the current colour
graphics port.  (Since the edit structure assumes the drawing environment specified in
the colour graphics port structure, setting the colour graphics port must be done before
the call to TENew to create the edit structure.)

The next two lines set the text size and font.  (These will be copied from the colour
graphics port to the edit structure when TENew is called.)

The next block creates a document structure and assigns a handle to that structure to the
window structure's refCon field.  The following line increments the global variable which
keeps track of the number of open windows.  GetNewControl creates a vertical scroll bar
and assigns a handle to it to the appropriate field of the document structure.  The next
block establishes the view and destination rectangles two pixels inside the window's port
rectangle less the scroll bar.

MoveHHi and HLock move the document structure high and lock it.  A monostyled edit
structure is then created by TENew and its handle is assigned to the appropriate field of
the document structure.  (If this call is not successful, the window and scroll bar are
disposed of, an error alert is displayed, and the function returns.)  The handle to the
document structure is then unlocked.

TESetClickLoop installs the universal procedure pointer to the custom click loop function
customClickLoop in the clickLoop field of the edit structure.  TEAutoView enables
automatic scrolling for the edit structure.  TEFeatureFlag enables outline highlighting
for the edit structure.

The last line returns a pointer to the newly opened window.

customClickLoop

customClickLoop replaces the default click loop function so as to provide for scroll bar
adjustment in concert with automatic scrolling.  Following a mouse-down within the view
rectangle, customClickLoop is called repeatedly by TEClick as long as the mouse button
remains down.

The first three lines retrieve a handle to the edit structure associated with the window. 
The next two lines save the current colour graphics port and set the window's colour
graphics port as the current port.

The window's current clip region will have been set by TextEdit to be equivalent to the
view rectangle.  Since the scroll bar has to be redrawn, the clipping region must be
temporarily reset to include the scroll bar.  Accordingly, GetClip saves the current
clipping region and the following two lines set the clipping region to the bounds of the
coordinate plane.  

GetMouse gets the current position of the cursor.  If the cursor is above the top of the
port rectangle, the text must be scrolled downwards.  Accordingly, the variable
linesToScroll is set to 1.  The subsidiary function doSetScrollBarValue (see below) is then
called to, amongst other things, reset the scroll bar's value.  Note that the value in
linesToScroll may be modified by doSetScrollBarValue.  If linesToScroll is not set to 0 by
doSetScrollBarValue, TEScroll is called to scroll the text by a number of pixels equivalent
to the value in the lineHeight field of the edit structure, and in a downwards direction.

If the cursor is below the bottom of the port rectangle, the same process occurs except
that the variable linesToScroll is set to -1, thus causing an upwards scroll of the text
(assuming that the value in linesToScroll is not changed to 0 by doSetScrollBarValue).

If scrolling has occurred, doDrawDataPanel redraws the data panel.  SetClip restores the
clipping region to that established by the view rectangle and SetPort restores the saved
colour graphics port.  Finally, the last line returns true.  (A return of false would
cause TextEdit to stop calling customClickLoop, as if the user had released the mouse
button.)

doSetScrollBarValue

doSetScrollBarValue is called from customClickLoop.  Apart from setting the scroll 
bar's value so as to cause the scroll box to follow up automatic scrolling, the function 
checks whether the limits of scrolling have been reached.

The first two lines get the current control value and the current control maximum value. 
At the next block, the value in the variable linesToScroll will be set to either 0 (if
the current control value is 0) or equivalent to the control maximum value (if the
current control value is equivalent to the control maximum value.  If these modifications
do not occur, the value in linesToScroll will remain as established at the first line in
this block, that is, the current control value minus the value in linesToScroll as passed
to the function.

SetControlValue sets the control's value to the value in linesToScroll.  The last line
sets the value in linesToScroll to 0 if the limits of scrolling have already been
reached, or to the value as it was when the doSetScrollBarValue function was entered.

doAdjustMenus

doAdjustMenus adjusts the menus.  Much depends on whether any windows are currently open.

If at least one window is open, Lines The first three lines in the if block get a handle
to the edit structure associated with the front window and the first call to EnableItem
enables the Close item in the File menu.  If there is a current selection range, the Cut,
Copy, and Clear items are enabled, otherwise they are disabled.  If there is any text in
the desk scrap (the call to GetScrap), the Paste item is enabled, otherwise it is
disabled.  If there is any text in the edit structure, the SaveAs and Select All items
are enabled, otherwise they are disabled.

If no windows are open, the Close, SaveAs, Clear, and Select All items are disabled.
doFileMenu

doFileMenu handles File menu choices, calling the appropriate application-defined
functions according to the menu item chosen.  In the SaveAs case, a handle to the edit
structure associated with the front window is retrieved and passed as a parameter to the
appropriate application-defined function.

(Note that, because TextEdit, rather than file operations, is the real focus of this
program, the file-related code has been kept to a minimum, even to the extent of having
no Save-related, as opposed to SaveAs-related, code.)

doEditMenu

doEditMenu handles choices from the Edit menu.  Recall that, in the case of monostyled
edit structures, TECut, TECopy, and TEPaste do not copy/paste text to/from the desk
scrap.  This program, however, supports copying/pasting to/from the desk scrap.

Before the usual switch is entered, a handle to the edit structure associated with the
front window is retrieved.

The iCut case handles the Cut command.  Firstly, the call to ZeroScrap attempts to empty
the desk scrap.  If the call succeeds, PurgeSpace establishes the size of the largest
block in the heap that would be available if a general purge were to occur.  The next
line gets the current selection length.  If the selection length is greater than the
available memory, the user is advised via an error message. Otherwise, TECut is called to
remove the selected text from the edit structure and copy it to the TextEdit private
scrap.  The scroll bar is adjusted, and TEToScrap is called to copy the private scrap to
the desk scrap.  If the latter call is not successful, ZeroScrap cleans up as best it can
by emptying the desk scrap.

The iCopy case handles the Copy command.  If the call to empty the desk scrap is
successful, TECopy is called to copy the selected text from the edit structure to the
TextEdit private scrap.  TEToScrap then copies the private scrap to the desk scrap.

The iPaste case handles the Paste command, which must not proceed if the paste would
cause the TextEdit limit of 32,767 bytes to be exceeded.  The first line establishes a
value equal to the number of bytes in the edit structure plus the number of bytes of the
specified data type ('TEXT') in the desk scrap.  If this value exceeds the TextEdit
limit, the user is advised via an error message.  Otherwise, TEFromScrap copies the desk
scrap to TextEdit's private scrap, TEPaste inserts the private scrap into the edit
structure, and the following line adjusts the scroll bar.

The iClear case handles the Clear command.  TEDelete deletes the current selection range
from the edit structure and the following line adjusts the scroll bar.

The iSelectAll case handle the Select All command.  TESetSelect sets the selection range
according to the first two parameters (selStart and selEnd).

doGetSelectLength

doGetSelectLength returns a value equal to the length of the current selection. 

doAdjustScrollbar

doAdjustScrollbar adjusts the vertical scroll bar.

The first two lines retrieve handles to the document structure and edit structure
associated with the window in question.

At the next block, the value in the nLines field of the edit structure is assigned to the
numberOfLines variable.  The next action is somewhat of a refinement and is therefore not
essential.  If the last character in the edit structure is the return character,
numberOfLines is incremented by one.  This will ensure that, when the document is
scrolled to its end, a single blank line will appear below the last line of text.

At the next block, the variable controlMax is assigned a value equal to the number of
lines in the edit structure less the number of lines which will fit in the view
rectangle.  If this value is less than 0 (indicating that the number of lines in the edit
structure is less than the number of lines that will fit in the view rectangle),
controlMax is set to 0.  SetControlMaximum then sets the control maximum value.  If
controlMax is 0, the scroll bar is automatically unhighlighted by the SetControlMaximum
call.

The first line of the next block assigns to the variable controlValue a value equal to
the number of text lines that the top of the destination rectangle is currently "above"
the top of the view rectangle.  If the calculation returns a value less than 0 (that is,
the document has been scrolled fully down), controlValue is set to 0.  If the calculation
returns a value greater than the current control maximum value (that is, the document has
been scrolled fully up), controlValue is set to equal that value.  SetControlValue sets
the control value to the value in controlValue.  For example, if the top of the view
rectangle is 2, the top of the destination rectangle is -34 and the lineHeight field of
the edit structure contains the value 13, the control value will be set to 3.

If the target is the PowerPC target, and if Mac OS 8.5 or later is present, 
SetControlViewSize is called to advise the Control Manager of the height of the view 
rectangle.  If Smart Scrolling is selected on in the Options tab of the Mac OS 8.5 and 
later Appearance control panel, this will cause the scroll boxes of the scroll bar to be
proportional.

With the control maximum value and the control value set, TEScroll is called to make sure
the text is scrolled to the position indicated by the scroll box.  Extending the example
in the previous paragraph, the second parameter in the TEScroll call is 2 - (34 - (3 *
13)), that is, 0.  In that case, no corrective scrolling actually occurs.

doAdjustCursor

doAdjustCursor adjusts the cursor to the I-Beam shape when the cursor is over the content
region less the scroll bar area, and to the arrow shape when the cursor is outside that
region.  It is similar to the cursor adjustment function in the demonstration program at
Chapter 13 - Offscreen Graphics Worlds, Pictures, Cursors, and Icons. 

doCloseWindow

doCloseWindow disposes of the specified window.  The associated scroll bar, the
associated edit structure and the associated document structure are disposed of before
the call to DisposeWindow.

doSaveAsFile, doOpenCommand, doOpenFile

The functions doSaveAsFile, doOpenCommand, and doOpenFile are document saving and opening
functions, enabling the user to open and save 'TEXT' documents.  Since the real focus of
this program is TextEdit, not file operations, the code is "bare bones" and as brief as
possible.

For a complete example of opening and saving monostyled 'TEXT' documents, see the
demonstration program at Chapter 16 - Files.

doDrawDataPanel

doDrawDataPanel draws the data panel at the bottom of each window.  Displayed in this
panel are the values in the teLength, nLines, lineHeight and destRect.top fields of the
edit structure and the contrlValue and contrlMax fields of the scroll bar's control
structure.

doHelpMenu

doHelpMenu handles a choice of the Text1 Help item added by this program to the
system-managed Help Menu.  The code reflects the fact that Apple reserves the right to
add items to the Help menu in future versions of the system software.

HMGetHelpMenuHandle gets a handle to the Help menu structure.  CountMenuItems returns the
number of items in the Help menu.  Since we know that we have added one item to this
menu, the next line will establish the original number of help items.  If the value
passed to the doHelpMenu function is greater than this, it must therefore represent the
item number of our Text1 Help item, in which case the application-defined function which
opens the help dialog is called.

HelpDialog.c

#define

Constants are established for the 'DLOG' resource ID and items in the dialog, the index
of error strings within a 'STR#' resource, and 'TEXT', 'styl', and 'PICT' resource IDs. 
kTextInset which will be used to inset the view and destination rectangles a few pixels
inside the user pane's rectangle.

#typedef

The first two data types are for a picture information structure and a document
structure.  (Note that one field in the document structure is a pointer to a picture
information structure.)  The third data type will be used for saving and restoring the
background colour and pattern.

doHelp

doHelp is called when the user chooses the Text1 Help item in the Help menu.

The dialog will utilise an event filter function, a user pane drawing function, and an
action function.  The first three lines create the associated routine descriptors.

GetNewDialog creates the dialog.  NewHandle creates a block for a document structure and
the handle is assigned to the dialog's window structure's refCon field.

SetDialogDefaultItem sets the dialog's push button as the default item and ensures that
the default ring will be drawn around that item.

At the next block, SetControlData sets the user pane drawing function.

At the next block, the destination and view rectangles are both made equal to the user
pane's rectangle, but inset four pixels from the left and right and two pixels from the
top and bottom.  The call to TEStyleNew creates a multistyled edit structure based on
those two rectangles.

The next block assigns the handle to the scrollbar to the appropriate field of the
dialog's document structure.

A pointer to a picture information structure will eventually be assigned to a field in
the document structure.  For the moment, that field is set to NULL.  

At the next block, two global variables are assigned the resource IDs relating to the
first Help topic's 'TEXT'/'styl' resource and associated 'PICT' resources.

The next block calls the application-defined function doGetText which, amongst other
things, loads the specified 'TEXT'/'styl' resources and inserts the text and style
information into the edit structure.  

The next block calls the application-defined function doGetPictureInfo which, amongst
other things, searches for option-space characters in the 'TEXT' resource and, if
option-space characters are found, loads a like number of 'PICT' resources beginning with
the specified ID.

NewRgn creates an empty region, which will be used to save the dialog's colour graphic's
port's clipping region.

To complete the initial setting up, ShowWindow is called to makes the dialog visible and
the application-defined function doSaveBackground is called to save the background colour
and pattern of the dialog as they were when the dialog was created.

The do-while ModalDialog loop continues until the user clicks the "Done" (OK) button or
hits the Return key.

The modal dialog uses an application-defined filter function which, as will be seen,
handles mouse-down events within the scrollbar.  The only other event of interest is a
hit on the pop-up menu button.  Accordingly, if ModalDialog returns a hit on that
control, SetControlValue is called to set the scroll bar's value to 0, the menu item
chosen is determined, and the switch assigns the appropriate 'TEXT'/'styl' and 'PICT'
resource IDs to the global variables which keep track of which of those resources are to
be loaded and displayed.

Before anything is drawn in that part of the dialog used to display the text and
pictures, the background colour and pattern are set to, respectively, the white colour
and the white pattern by a call to doSetBackgroundWhite.  The next block then perform the
same "get text" and "get picture information" actions as were preformed at start-up, but
this time with the 'TEXT'/'styl' and 'PICT' resources as determined within the preceding
switch.

The call to doDrawPictures draws any pictures that might initially be located in the view
rectangle.  With all drawing completed, the application-defined function
doRestoreBackground is called to restore the saved background colour and pattern.

When the user clicks the "Done" (OK) button or hits the Return key, application-defined
functions are called to close down the Help dialog and dispose of the routine
descriptors.

doCloseHelp

doCloseHelp closes down the Help dialog.

The first two lines retrieve a handle to the dialog's document structure.  The next two
lines dispose of region used to save the clipping region.  TEDispose disposes of the edit
structure.  The next block disposes of any 'PICT' resources currently in memory, together
with the picture information structure.  Finally, the dialog's document structure is
disposed of, the dialog itself is disposed of, and the graphics port saved in doHelp is
restored.

userPaneDrawFunction

userPaneDrawFunction is the user pane drawing function set within doHelp.  It will be
called automatically whenever the dialog receives an update event.

The first block gets a pointer to the dialog to which the user pane control belongs, gets
the user pane's rectangle, insets that rectangle by one pixel all round, and then further
expands it to the right to the right edge of the scroll bar.  At the next block, a list
box frame is drawn in the appropriate state, depending on whether the window is the
movable modal dialog is currently the active window.  (The first call to
DrawThemeListBoxFrame is required so as to ensure that the list box frame is drawn when
the dialog is initially displayed.)

The next block erases the previously defined rectangle with the white colour using the
white pattern.

The three lines retrieve the view rectangle from the edit structure.  The call to
TEUpdate draws the text in the edit structure in the view rectangle.  The call to
drawPictures draws any pictures which might currently be located in the view rectangle.

The last line restores the background colour and pattern saved in doHelp.

doGetText

doGetText is called when the dialog is first opened and when the user chooses a new item
from the pop-up menu.  Amongst other things, it loads the 'TEXT'/'styl' resources
associated with the current menu item and inserts the text and style information into the
edit structure.

The first two lines get a handle to the edit structure.  The next two lines set the
selection range to the maximum value and then delete that selection.  The destination
rectangle is then made equal to the view rectangle and the scroll bar's value is set to
0.

GetResource is called twice to load the specified 'TEXT'/'styl' resources, following
which TEStyleInsert is called to insert the text and style information into the edit
structure.  Two calls to ReleaseResource then release the 'TEXT'/'styl' resources.

The next block gets the total height of the text in pixels.

At the next block, if the height of the text is greater than the height of the view
rectangle, the local variable heightToScroll is made equal to total height of the text
minus the height of the view rectangle.  This value is then used to set the scroll bar's
maximum value.  The scroll bar is then made active.

If the target is the PowerPC target, and if Mac OS 8.5 or later is present, 
SetControlViewSize is called to advise the Control Manager of the height of the view 
rectangle.  If Smart Scrolling is selected on in the Options tab of the Mac OS 8.5 and 
later Appearance control panel, this will cause the scroll boxes of the scroll bar to be 
proportional.

If the height of the text is less than the height of the view rectangle, the scroll bar
is made inactive.

true is returned if the GetResource calls did not return with false.

doGetPictureInfo

doGetPictureInfo  is called after getText when the dialog is opened and when the 
user chooses a new item from the pop-up menu.  Amongst other things, it searches for
option-space characters in the 'TEXT' resource and, if option-space characters are found,
loads a like number of 'PICT' resources beginning with the specified ID.

The first line gets a handle to the dialog's document structure.  

If the picInfoRecPtr field of the document structure does not contain NULL, the currently
loaded 'PICT' resources are released, the picture information structures are disposed of,
and the picInfoRecPtr field of the document structure is set to NULL.  

The next line sets to 0 the field of the document structure which keeps track of the
number of pictures associated with the current 'TEXT' resource.

The next two lines get a handle to the edit structure, then a handle to the block
containing the actual text.  This latter is then used at to assign the size of that block
to a local variable.  After two local variables are initialised, the block containing the
text is locked.

The next block counts the number of option-space characters in the text block.  At the
following block, if there are no option-space characters in the block, the block is
unlocked and the function returns.

A call to NewPtr then allocates a nonrelocatable block large enough to accommodate a
number of picture information structures equal to the number of option-space characters
found.  The pointer to the block is then assigned to the appropriate field of the
dialog's document structure.

The next line resets the offset value to 0.

The for loop repeats for each of the option-space characters found.  GetPicture loads the
specified 'PICT' resource (the resource ID being incremented from the base ID at each
pass through the loop) and assigns the handle to appropriate field of the relevant
picture information structure.  Munger finds the offset to the next option-space
character and TEGetPoint gets the point, based on the destination rectangle, of the
bottom left of the character at that offset.  TEGetStyle is called to obtain the line
height of the character at the offset and this value is subtracted from the value in the
point's v field.  The offset is incremented and the rectangle in the picture structure's
picFrame field is assigned to the bounds field of the picture information structure.  The
next block then offsets this rectangle so that it is centred laterally in the destination
rectangle with its top offset from the top of the destination rectangle by the amount
established at the line picturePoint.v -= lineHeight;.

The third last line assigns the number of pictures loaded to the appropriate field of the
dialog's document structure.  The block containing the text is then unlocked.  The
function returns true if false has not previously been returned within the for loop.

actionFunction

actionFunction is the action function called from within the event filter function
eventFilter.  It is repeatedly called by TrackControl while the mouse button remains down
within the scroll bar.  Its ultimate purpose is to determine the new scrollbar value when
the mouse-down is within the scroll arrows or gray areas of the scroll bar, and then call
a separate application-defined function to effect the actual scrolling of the text and
pictures based on the new scrollbar value.  (The scroll bar is the live scrolling
variant, so the CEDF automatically updates the control's value while the mouse remains
down in the scroll box.)

Firstly, if the cursor is still not within the control, execution falls through to the
bottom of the function and the action function exits.

The first block gets a pointer to the owner of the scrollbar, retrieves a handle to the
dialog's document structure,  gets a handle to the edit structure, gets the view
rectangle, and assigns a value to the h field of a point variable equal to the left of
the view rectangle plus 4 pixels.

The switch executes only if the mouse-down is not in the scroll box.

In the case of the Up scroll arrow, the variable delta is assigned a value which will
ensure that, after the scroll, the top of the incoming line of text will be positioned
cleanly at top of the view rectangle.

In the case of the Down scroll arrow, the variable delta is assigned a value which will
ensure that, after the scroll, the bottom of the incoming line of text will be positioned
cleanly at bottom of the view rectangle.

In the case of the Up gray area, the variable delta is assigned a value which will ensure
that, after the scroll, the top of the top line of text will be positioned cleanly at the
top of the view rectangle and the line of text which was previously at the top will still
be visible at the bottom of the view rectangle.

In the case of the Down gray area, the variable delta is assigned a value which will
ensure that, after the scroll, the bottom of the bottom line of text will be positioned
cleanly at the bottom of the view rectangle and the line of text which was previously at
the bottom will still be visible at the top of the view rectangle.

The first line after the switch gets the pre-scroll scroll bar value.  If the text is not
fully scrolled up and a scroll up is called for, or if the text is not fully scrolled
down and a scroll down is called for, the current clipping region is saved, the clipping
region is set to the dialog's port rectangle, the scroll bar value is set to the required
new value, and the saved clipping region is restored.  (TextEdit may have set the
clipping region to the view rectangle, so it must be changed to include the scroll bar
area, otherwise the scroll bar will not be drawn.)

With the scroll bar's new value set and the scroll box redrawn in its new position, the
application-defined function for scrolling the text and pictures is called.  Note that
this last line will also be called if the mouse-down was within the scroll box.

doScrollTextAndPicts

doScrollTextAndPicts is called from actionFunction.  It scrolls the text within the view
rectangle and calls another application-defined function to draw any picture whose
rectangle intersects the "vacated" area of the view rectangle.

The first line sets the background colour to white and the background pattern to white.

The next two lines get a handle to the edit structure.  The next line determines the
difference between the top of the destination rectangle and the top of the view rectangle
and the next subtracts from this value the scroll bar's new value.  If the result is
zero, the text must be fully scrolled in one direction or the other, so the function
simply returns.

If the text is not already fully scrolled one way or the other, TEScroll scrolls the text
in the view rectangle by the number of pixels determined at the fifth line.

If there are no pictures associated with the 'TEXT' resource in use, the function returns
immediately after the text is scrolled.

The next consideration is the pictures and whether any of their rectangles, as stored in
the picture information structure, intersect the area of the view rectangle "vacated" by
the scroll. At the if/else block, a rectangle is made equal to the "vacated" area of the
view rectangle, the if block catering for the scrolling up case and the else block
catering for the scrolling down case.  (Of course, if the scroll box has been dragged by
the user over a large distance, the "vacated" area will equate to the whole view
rectangle.)  This rectangle is passed as a parameter in the call to drawPictures.

doDrawPictures

doDrawPictures determines whether any pictures intersect the rectangle passed to it as a
formal parameter and draws any pictures that do.

The first two lines get handles to the dialog's document structure and the edit
structure.  

The next line determines the difference between the top of the destination rectangle and
the top of the view rectangle.  This will be used later to offset the picture's rectangle
from destination rectangle coordinates to view rectangle coordinates.  The next line
determines the number of pictures associated with the current 'TEXT' resource, a value
which will be used to control the number of passes through the following for loop.

Within the loop, the picture's rectangle is retrieved from the picture information
structure and offset to the coordinates of the view rectangle.  SectRect determines
whether this rectangle intersects the rectangle passed to drawPictures from
scrollTextAndPicts.  If it does not, the loop returns for the next iteration.  If it
does, the picture's handle is retrieved, LoadResource checks whether the 'PICT' resource
is in memory and, if necessary, loads it, HLock locks the handle, DrawPicture draws the
picture, and HUnlock unlocks the handle.  Before DrawPicture is called, the clipping
region is temporarily adjusted to equate to the rectangle passed to drawPictures from
scrollTextAndPicts so as to limit drawing to that rectangle.

eventFilter

eventFilter is the application-defined filter function for the dialog.  It is essentially
the same as the event filter introduced in the demonstration program Dialogs (Chapter 8)
except that it also intercepts mouse-down events.

In the case of a mouse-down event, the location of the event is determined in local
coordinates.  If FindControl and the following two lines determine that the mouse-down
was within the scrollbar, TrackControl is called to handle user interaction while the
mouse button remains down.  While the mouse button remains down, TrackControl continually
calls actionFunction.

doSaveBackground, doRestoreBackground, and doSetBackgroundWhite

doSaveBackground and doRestoreBackground are essentially the same as the functions
introduced at Chapter 12 - Drawing With QuickDraw for saving and restoring the drawing
environment.  In this case, the saving and restoring is limited to the background colour
and the background bit or pixel pattern.  doSetBackgroundWhite sets the background colour
to white and the background pattern to the pattern white.

Go to Part 2
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasnā€™t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, donā€™t remind me I could currently buy him this month Iā€™m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly ā€œborrowedā€ many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »

Price Scanner via MacPrices.net

13-inch M2 MacBook Airs in stock today at App...
Apple has 13ā€³ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Appleā€™s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Appleā€™s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
Weā€™ve updated our iPhone Price Tracker with the latest carrier deals on Appleā€™s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobileā€™s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobileā€™s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13ā€³ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Appleā€™s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16ā€³ and 14ā€³ MacBook Pros along with 13ā€³ and 15ā€³ MacBook... Read more
Every model of Appleā€™s 13-inch M3 MacBook Air...
Best Buy has Apple 13ā€³ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13ā€³ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9ā€³ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
ā€¦relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.