TweetFollow Us on Twitter

MACINTOSH C

Demonstration Program

Go to Contents
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// Printing.h
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// 
// This program:
//
// Ą  Opens a window in which the contents of the main fields of the TPrint, TPrJob,
//    TPrStl and TPrInfo structures are displayed when the user dismisses the style 
//    dialog box, and in which the first part of a document is displayed when the user
//    hits the Print button in the job dialog box.
//
// Ą  Adds a pop-up menu button, three radio buttons, a checkbox, and a group box to the
//    job dialog box. 
//
// Ą  Allows the user to print a document containing a picture and text, with the text
//    being printed in the font and font size, and with the fractional widths setting, 
//    specified using the items added to the job dialog box.
//
// The program utilises the following resources:
//
// Ą  'MBAR' resource and associated 'MENU' resources (preload, non-purgeable).
//
// Ą  A 'WIND' resource (purgeable).
//
// Ą  A 'TEXT' resource (non-purgeable) used for printing.
//
// Ą  A 'PICT' resource (non-purgeable) used for printing.
//
// Ą  'CNTL' resources (purgeable) for controls added to the job dialog box.
//
// Ą  A 'DITL' resource (purgeable) specifying the items to be appended to the job 
//    dialog box.
//
// Ą  A 'MENU' resource (preload, non-purgeable) for the pop-up menu button.
//
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

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

#include <Appearance.h>
#include <ControlDefinitions.h>
#include <Devices.h>
#include <Printing.h>
#include <Processes.h>
#include <Resources.h>
#include <ToolUtils.h>

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

#define mApple                128
#define mFile                 129
#define  iQuit                11
#define  iPageSetup           8
#define  iPrint               9
#define rMenubar              128
#define rWindow               128
#define rText                 128
#define rPicture              128
#define rJobDialogAppendDITL  128
#define  iPopupButton         1
#define  iRadioButton10pt     2
#define  iRadioButton12pt     3
#define  iRadioButton14pt     4
#define  iCheckboxFracWidths  5
#define kMargin               90
#define MAXLONG               0x7FFFFFFF
#define MIN(a,b)              ((a) < (b) ? (a) : (b))

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

void    main                  (void);
void    doInitManagers        (void);
void    doEvents              (EventRecord *);
void    doUpdate              (EventRecord *);
void    doMenuChoice          (long);
void    doPrinting            (void);
OSErr   doCreatePrintRecord   (void);
void    doPrStyleDialog       (void);
SInt16  doCalcNumberOfPages   (Rect);
void    doDrawPage            (Rect,SInt16,SInt16);
SInt16  doGetPageOrientation  (void);
Boolean doIsPrGeneralThere    (void);
void    doPrintRecordsInfo    (void);
void    doDrawRectStrings     (Str255,SInt16,SInt16,Str255,SInt16,SInt16,Str255);
void    doDrawPageOrientation (void);
void    doErrorAlert          (SInt16,Boolean);
void    doConcatPStrings      (Str255,Str255);

pascal TPPrDlg  initialisationFunction  (THPrint);
void            doAppendTheDITL         (TPPrDlg);
pascal void     itemEvaluationFunction  (TPPrDlg,SInt16);
pascal Boolean  eventFilter             (DialogPtr,EventRecord *,SInt16 *);

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// Printing.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

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

#include "Printing.h"

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

THPrint    gTPrintHdl;
WindowPtr  gWindowPtr;
Boolean    gDone;
Boolean    gPrintStructureInited        = false;
Boolean    gInhibitPrintStructuresInfo  = false;
TEHandle   gEditRecHdl;
Handle     gTextHdl;
PicHandle  gPictureHdl;
SInt16     gFontNumber;
SInt16     gFontSize;
RGBColor   gWhiteColour  = { 0xFFFF, 0xFFFF, 0xFFFF };
RGBColor   gBlackColour  = { 0x0000, 0x0000, 0x0000 };
RGBColor   gBlueColour   = { 0x1818, 0x4B4B, 0x8181 };

extern TPPrDlg         gTPrDlgStructurePtr;
extern PDlgInitUPP     gInitialisationFunctionUPP;
extern PItemUPP        gNewItemEvaluateFunctionUPP;
extern ModalFilterUPP  gEventFilterUPP;

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

void  main(void)
{
  Handle       menubarHdl;
  MenuHandle   menuHdl;
  EventRecord  eventStructure;
  Boolean      gotEvent;

  // ...................... initialise managers

  doInitManagers();

  // .................... set up menu bar and menus

  if(!(menubarHdl = GetNewMBar(rMenubar)))
    ExitToShell();
  SetMenuBar(menubarHdl);
  DrawMenuBar();

  if(!(menuHdl = GetMenuHandle(mApple)))
    ExitToShell();
  else
    AppendResMenu(menuHdl,'DRVR');

  // ........................ open window

  if(!(gWindowPtr = GetNewCWindow(rWindow,NULL,(WindowPtr)-1)))
    ExitToShell();

  SetPort(gWindowPtr);
  TextSize(10);
  RGBBackColor(&gBlueColour);
  EraseRect(&gWindowPtr->portRect);  

  // ................. load 'TEXT' and 'PICT' resources
  
  gTextHdl = GetResource('TEXT',rText);
  if(gTextHdl == NULL)
    ExitToShell();

  gPictureHdl = GetPicture(rPicture);
  if(gPictureHdl == NULL)
    ExitToShell();

  // ......................... event loop

  gDone = false;
  
  while(!gDone)
  {
    gotEvent = WaitNextEvent(everyEvent,&eventStructure,MAXLONG,NULL);
    if(gotEvent)
      doEvents(&eventStructure);
  }
}

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

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

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

  InitCursor();  
  FlushEvents(everyEvent,0);

  RegisterAppearanceClient();
}

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

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

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

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

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

        case inGoAway:
          if(TrackGoAway(windowPtr,eventStrucPtr->where) == true)
            gDone = true;
          break;
      }
      break;

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

    case updateEvt:
      doUpdate(eventStrucPtr);
      break;

    case activateEvt:
      doPrintRecordsInfo();
      break;
  }
}

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

void  doUpdate(EventRecord *eventStrucPtr)
{
  WindowPtr  windowPtr;

  windowPtr = (WindowPtr) eventStrucPtr->message;

  BeginUpdate(windowPtr);
  doPrintRecordsInfo();
  EndUpdate(windowPtr);
}

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

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

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

  if(menuID == 0)
    return;

  switch(menuID)
  {
    case mApple:
      GetMenuItemText(GetMenuHandle(mApple),menuItem,itemName);
      daDriverRefNum = OpenDeskAcc(itemName);
      break;
      
    case mFile:
      if(menuItem == iPageSetup)
      {
        gInhibitPrintStructuresInfo = false;
        doPrStyleDialog();
      }
      else if(menuItem == iPrint)
        doPrinting();
      else if(menuItem == iQuit)
        gDone = true;
      break;
  }

  HiliteMenu(0);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doPrinting

void  doPrinting(void)
{
  GrafPtr   oldPort;
  SInt16    printError;
  Boolean   userClickedOK;
  SInt16    numberOfPages, numberOfCopies;
  SInt16    firstPage, lastPage, copy, page;
  TPPrPort  printPortPtr;
  TPrStatus tprStatus;

  GetPort(&oldPort);

  PrOpen();

  if(PrError() == noErr)
  {
    if(!gPrintStructureInited)
      printError = doCreatePrintRecord();
    else
      printError = noErr;
    
    gTPrDlgStructurePtr = PrJobInit(gTPrintHdl);
    printError = PrError(); 

    if(printError == noErr)
    {
      gInitialisationFunctionUPP  = NewPDlgInitProc((ProcPtr) initialisationFunction);
      gNewItemEvaluateFunctionUPP = NewPItemProc((ProcPtr) itemEvaluationFunction);
      gEventFilterUPP             = NewModalFilterProc((ProcPtr) eventFilter);

      userClickedOK = PrDlgMain(gTPrintHdl,gInitialisationFunctionUPP);

      DisposeRoutineDescriptor(gInitialisationFunctionUPP);
      DisposeRoutineDescriptor(gNewItemEvaluateFunctionUPP);
      DisposeRoutineDescriptor(gEventFilterUPP);

      if(userClickedOK)
      {
        gInhibitPrintStructuresInfo = true;

        numberOfPages = doCalcNumberOfPages((*gTPrintHdl)->prInfo.rPage);
        
        numberOfCopies = (*gTPrintHdl)->prJob.iCopies;
        firstPage = (*gTPrintHdl)->prJob.iFstPage;
        lastPage = (*gTPrintHdl)->prJob.iLstPage;

        (*gTPrintHdl)->prJob.iFstPage = 1;
        (*gTPrintHdl)->prJob.iLstPage = iPrPgMax;

        if(numberOfPages < lastPage)
          lastPage = numberOfPages;
        
        for(copy=1;copy<numberOfCopies+1;copy++)
        {
          for(page=firstPage;page<lastPage+1;page++)
          {
            if((page - firstPage) % iPFMaxPgs == 0)
            {  
              if(page != firstPage)
              {
                PrCloseDoc(printPortPtr);
                if(((*gTPrintHdl)->prJob.bJDocLoop == bSpoolLoop) && 
                    (PrError() == noErr))
                  PrPicFile(gTPrintHdl,NULL,NULL,NULL,&tprStatus);
              }
              printPortPtr = PrOpenDoc(gTPrintHdl,NULL,NULL);
            }  
            if(PrError() == noErr)
            {
              PrOpenPage(printPortPtr,NULL);
              if(PrError() == noErr)
                doDrawPage((*gTPrintHdl)->prInfo.rPage,page,numberOfPages);
              PrClosePage(printPortPtr);
            }
          }

          PrCloseDoc(printPortPtr);

          if(((*gTPrintHdl)->prJob.bJDocLoop == bSpoolLoop) && (PrError() == noErr))
            PrPicFile(gTPrintHdl,NULL,NULL,NULL,&tprStatus);
        }
      }
    }
  }

  printError = PrError();

  PrClose();

  if(printError != noErr && printError != iPrAbort)
    doErrorAlert(printError,false);

  SetPort(oldPort);
  doPrintRecordsInfo();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCreatePrintRecord

OSErr  doCreatePrintRecord(void)
{
  SInt16  printError;

  gTPrintHdl = (THPrint) NewHandleClear(sizeof(TPrint));
  if(gTPrintHdl != NULL )
  {
    PrintDefault(gTPrintHdl);
    printError = PrError();
    if(printError == noErr)
      gPrintStructureInited = true;
    return(printError);
  }
  else
    ExitToShell();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doPrStyleDialog

void  doPrStyleDialog(void)
{
  SInt16  printError;

  PrOpen();

  printError = PrError();
  if(printError == noErr)
  {
    if(!gPrintStructureInited)
    {
      printError = doCreatePrintRecord();
      if(printError != noErr)
        doErrorAlert(printError,true);
    }

    PrStlDialog(gTPrintHdl);
  }
  else
    doErrorAlert(printError,false);

  PrClose();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCalcNumberOfPages

SInt16  doCalcNumberOfPages(Rect pageRect)
{
  Rect    destRect, pictureRect;
  SInt16  heightDestRect, linesPerPage, numberOfPages;

  RGBForeColor(&gBlackColour);
  RGBBackColor(&gWhiteColour);
  EraseRect(&(gWindowPtr->portRect));  

  SetRect(&destRect,pageRect.left + kMargin,pageRect.top + (kMargin * 1.5), 
          pageRect.right - kMargin,pageRect.bottom - (kMargin * 1.5));
  OffsetRect(&destRect,- (kMargin - 5),- ((kMargin * 1.5) - 5));

  TextFont(gFontNumber);
  TextSize(gFontSize);

  gEditRecHdl = TENew(&destRect,&destRect);
  TEInsert(*gTextHdl,GetHandleSize(gTextHdl),gEditRecHdl);

  heightDestRect = destRect.bottom - destRect.top;
  linesPerPage = heightDestRect / (*gEditRecHdl)->lineHeight; 
  numberOfPages = ((*gEditRecHdl)->nLines / linesPerPage) + 1;  

  SetRect(&pictureRect,destRect.left,destRect.top,
        destRect.left + ((*gPictureHdl)->picFrame.right - (*gPictureHdl)->picFrame.left),
        destRect.top + ((*gPictureHdl)->picFrame.bottom - (*gPictureHdl)->picFrame.top));  
  DrawPicture(gPictureHdl,&pictureRect);

  return(numberOfPages);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDrawPage

void  doDrawPage(Rect pageRect,SInt16 pageNumber,SInt16 numberOfpages)
{
  Rect      destRect, pictureRect;
  SInt16    heightDestRect, linesPerPage, numberOfLines;
  TEHandle  pageEditRecHdl;
  Handle    textHdl;
  SInt32    startOffset, endOffset;
  Str255    theString;
  
  SetRect(&destRect,pageRect.left + kMargin,pageRect.top + (kMargin * 1.5),
          pageRect.right - kMargin,pageRect.bottom - (kMargin * 1.5));

  heightDestRect = destRect.bottom - destRect.top;
  linesPerPage = heightDestRect / (*gEditRecHdl)->lineHeight; 
  numberOfLines = (*gEditRecHdl)->nLines;
  
  TextFont(gFontNumber);
  TextSize(gFontSize);

  pageEditRecHdl = TENew(&destRect,&destRect);
  textHdl = (*gEditRecHdl)->hText;

  startOffset = (*gEditRecHdl)->lineStarts[(pageNumber - 1) * linesPerPage];
  if(pageNumber == numberOfpages)
    endOffset = (*gEditRecHdl)->lineStarts[numberOfLines];
  else
    endOffset = (*gEditRecHdl)->lineStarts[pageNumber * linesPerPage];

  HLock(textHdl);
  TEInsert(*textHdl + startOffset,endOffset - startOffset,pageEditRecHdl);
  HUnlock(textHdl);

  if(pageNumber == 1)
  {
    SetRect(&pictureRect,destRect.left,destRect.top,
        destRect.left + ((*gPictureHdl)->picFrame.right - (*gPictureHdl)->picFrame.left),
        destRect.top + ((*gPictureHdl)->picFrame.bottom - (*gPictureHdl)->picFrame.top));  

    DrawPicture(gPictureHdl,&pictureRect);
  }

  MoveTo(destRect.left,pageRect.bottom - 25);
  NumToString((SInt32) pageNumber,theString);
  DrawString(theString);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× drawPageOrientation

void  doDrawPageOrientation(void)
{
  SInt16  orientation;

  MoveTo(20,260);
  DrawString("\pOrientation selected:");

  orientation = doGetPageOrientation();

  MoveTo(190,260);    
  if(orientation == 1)
    DrawString("\pLandscape");
  else if(orientation == 2)
    DrawString("\pPortrait");
  else
    DrawString("\p(PrGeneral not supported by driver)");
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doGetPageOrientation

SInt16  doGetPageOrientation(void)
{
  TGetRotnBlk getRotRec;

  if(doIsPrGeneralThere)
  {
    getRotRec.iOpCode = getRotnOp;
    getRotRec.hPrint = gTPrintHdl;
    PrGeneral((Ptr) &getRotRec);
    if((getRotRec.iError == noErr) && (PrError() == noErr) && getRotRec.fLandscape)
      return(1);
    else
      return(2);
  }
  else
    return(3);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doIsPrGeneralThere

Boolean  doIsPrGeneralThere(void)
{
  TGetRotnBlk  getRotRec;
  OSErr        printError;

  printError = 0;
  getRotRec.iOpCode = getRotnOp;
  getRotRec.hPrint = gTPrintHdl;

  PrGeneral((Ptr) &getRotRec);

  printError = PrError();
  PrSetError(noErr);

  if(printError == resNotFound)
    return(false);
  else
    return(true);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doPrintRecordsInfo

void  doPrintRecordsInfo(void)
{
  RGBColor  oldForeColour, oldBackColour;
  SInt16    fontNum;
  Str255    s2, s3;

  if(FrontWindow() == gWindowPtr && gPrintStructureInited && !gInhibitPrintStructuresInfo)
  {
    GetForeColor(&oldForeColour);
    GetBackColor(&oldBackColour);
    RGBForeColor(&gWhiteColour);
    RGBBackColor(&gBlueColour);
    EraseRect(&(gWindowPtr->portRect));  

    GetFNum("\pGeneva",&fontNum);
    TextFont(fontNum);
    TextSize(10);

    MoveTo(20,25);
    TextFace(bold);
    DrawString("\pFrom TPrint, TPrInfo and TPrStl structures:");
    TextFace(normal);

    NumToString((long) (*gTPrintHdl)->rPaper.top,s2);
    NumToString((long) (*gTPrintHdl)->rPaper.left,s3);
    doDrawRectStrings("\pPaper Rectangle (top,left):",20,45,s2,190,45,s3);

    NumToString((long) (*gTPrintHdl)->rPaper.bottom,s2);
    NumToString((long) (*gTPrintHdl)->rPaper.right,s3);
    doDrawRectStrings("\pPaper Rectangle (bottom,right):",20,60,s2,190,60,s3);

    NumToString((long) ((*gTPrintHdl)->prInfo.rPage).top,s2);
    NumToString((long) ((*gTPrintHdl)->prInfo.rPage).left,s3);
    doDrawRectStrings("\pPage Rectangle (top,left):",20,75,s2,190,75,s3);

    NumToString((long) ((*gTPrintHdl)->prInfo.rPage).bottom,s2);
    NumToString((long) ((*gTPrintHdl)->prInfo.rPage).right,s3);
    doDrawRectStrings("\pPage Rectangle (bottom,right):",20,90,s2,190,90,s3);

    MoveTo(20,105);
    DrawString("\pFeed Type:");
    MoveTo(190,105);
    if((*gTPrintHdl)->prStl.feed == 0)
      DrawString("\pCut sheet");
    else if((*gTPrintHdl)->prStl.feed == 1)
      DrawString("\pFanfold");

    MoveTo(20,120);
    DrawString("\pVertical resolution:");
    NumToString((long) (*gTPrintHdl)->prInfo.iVRes,s2);
    MoveTo(190,120);
    DrawString(s2);

    MoveTo(20,135);
    DrawString("\pHorizontal resolution:");
    NumToString((long) (*gTPrintHdl)->prInfo.iHRes,s2);
    MoveTo(190,135);
    DrawString(s2);

    MoveTo(20,155);
    TextFace(bold);
    DrawString("\pFrom TPrJob structure:");
    TextFace(normal);

    MoveTo(20,175);
    DrawString("\pFirst Page:");
    NumToString((long) (*gTPrintHdl)->prJob.iFstPage,s2);
    MoveTo(190,175);
    DrawString(s2);

    MoveTo(20,190);
    DrawString("\pLast Page:");
    NumToString((long) (*gTPrintHdl)->prJob.iLstPage,s2);
    MoveTo(190,190);
    DrawString(s2);

    MoveTo(20,205);
    DrawString("\pNumber of Copies:");
    NumToString((long) (*gTPrintHdl)->prJob.iCopies,s2);
    MoveTo(190,205);
    DrawString(s2);

    MoveTo(20,245);
    DrawString("\pNote:  Some printer drivers always set the iCopies field of the TPrJob");
    MoveTo(20,260);
    DrawString("\pstructure to 1 and handle multiple copies internally.");
  }

  RGBForeColor(&oldForeColour);
  RGBBackColor(&oldBackColour);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDrawRectStrings

void  doDrawRectStrings(Str255 s1,SInt16 x1,SInt16 y1,Str255 s2,SInt16 x2,
                        SInt16 y2,Str255 s3)
{
  MoveTo(x1,y1);
  DrawString(s1);
  MoveTo(x2,y2);
  DrawString("\p(");
  DrawString(s2);
  DrawString("\p,");
  DrawString(s3);
  DrawString("\p)");
}

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

void  doErrorAlert(SInt16 errorType,Boolean fatal)
{
  AlertStdAlertParamRec paramRec;
  Str255                theString, errorString = "\pPrinting Manager Error ";
  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;

  NumToString((long) errorType,theString);
  doConcatPStrings(errorString,theString);

  if(!fatal)
    StandardAlert(kAlertCautionAlert,errorString,NULL,¶mRec,&itemHit);
  else
  {
    StandardAlert(kAlertStopAlert,errorString,NULL,¶mRec,&itemHit);
    ExitToShell();
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doConcatPStrings

void  doConcatPStrings(Str255 targetString,Str255 appendString)
{
  SInt16  appendLength;

  appendLength = MIN(appendString[0],255 - targetString[0]);

  if(appendLength > 0)
  {
    BlockMoveData(appendString+1,targetString+targetString[0]+1,(SInt32) appendLength);
targetString[0] += appendLength;  
}
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// JobDialogAppend.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

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

#include "Printing.h"

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

TPPrDlg         gTPrDlgStructurePtr;
SInt32          gFirstAppendedItemNo;
PDlgInitUPP     gInitialisationFunctionUPP;
PItemUPP        gOldItemEvaluateFunctionUPP;
PItemUPP        gNewItemEvaluateFunctionUPP;
ModalFilterUPP  gEventFilterUPP;
extern SInt16   gFontNumber;
extern SInt16   gFontSize;

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× initialisationFunction

pascal TPPrDlg  initialisationFunction(THPrint hPrint)
{
  ControlHandle  controlHdl;
  MenuHandle    menuHdl;
  SInt16        numberOfItems, a, fontNumber;
  Str255        fontName;
    
  // ........................ append the DITL

  doAppendTheDITL(gTPrDlgStructurePtr);

  // ... make pop-up menu WYSIWYG, check second radio button, set fractional widths off

  GetDialogItemAsControl((DialogPtr) gTPrDlgStructurePtr,gFirstAppendedItemNo,
                         &controlHdl);
  GetControlData(controlHdl,kControlNoPart,kControlPopupButtonMenuHandleTag,
                 sizeof(menuHdl),(Ptr) &menuHdl,NULL);
  numberOfItems = CountMenuItems(menuHdl);
  for(a=1;a<=numberOfItems;a++)
  {
    GetMenuItemText(menuHdl,a,fontName);
    GetFNum(fontName,&fontNumber);
    SetMenuItemFontID(menuHdl,a,fontNumber);
  }

  GetDialogItemAsControl((DialogPtr) gTPrDlgStructurePtr,gFirstAppendedItemNo + 2,
                         &controlHdl);
  SetControlValue(controlHdl,1);
  gFontSize = 12;
    
   GetDialogItemAsControl((DialogPtr) gTPrDlgStructurePtr,gFirstAppendedItemNo + 4,
                          &controlHdl);
   SetControlValue(controlHdl,0);
  SetFractEnable(false);

  // ........ save old evaluation function and assign new evaluation function

  gOldItemEvaluateFunctionUPP = gTPrDlgStructurePtr->pItemProc;
  gTPrDlgStructurePtr->pItemProc = gNewItemEvaluateFunctionUPP;

  // ................. assign new event filter function

  gTPrDlgStructurePtr->pFltrProc = gEventFilterUPP;

  // ...... PrDlgMain expects a pointer to the modified dialog to be returned

  return gTPrDlgStructurePtr;
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doAppendTheDITL

void  doAppendTheDITL(TPPrDlg theDialog)
{
  Handle  ditlHdl;
  SInt16  numberOfExistingItems;

  ditlHdl = GetResource('DITL',rJobDialogAppendDITL);
  numberOfExistingItems = CountDITL((DialogPtr) theDialog);
  AppendDITL((DialogPtr) theDialog,(Handle) ditlHdl,appendDITLBottom);

  gFirstAppendedItemNo = numberOfExistingItems + 1;
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× itemEvaluationFunction

pascal void  itemEvaluationFunction(TPPrDlg theDialog,short itemHit)
{
  SInt16        localizedItemNo, controlValue;
  ControlHandle controlHdl;
  MenuHandle    menuHdl;
  Str255        itemName;
  
  localizedItemNo = itemHit - gFirstAppendedItemNo + 1;

  if(localizedItemNo > 0)
  {
     if(localizedItemNo == iPopupButton)
     {
      GetDialogItemAsControl((DialogPtr) theDialog,gFirstAppendedItemNo,&controlHdl);
      controlValue = GetControlValue(controlHdl);
      GetControlData(controlHdl,kControlNoPart,kControlPopupButtonMenuHandleTag,
                      sizeof(menuHdl),(Ptr) &menuHdl,NULL);
      GetMenuItemText(menuHdl,controlValue,itemName);
      GetFNum(itemName,&gFontNumber);
    }
    else if(localizedItemNo >= iRadioButton10pt && 
            localizedItemNo <= iRadioButton14pt)
    {
      GetDialogItemAsControl((DialogPtr) theDialog,gFirstAppendedItemNo + 1,&controlHdl);
       SetControlValue(controlHdl,0);
      GetDialogItemAsControl((DialogPtr) theDialog,gFirstAppendedItemNo + 2,&controlHdl);
       SetControlValue(controlHdl,0);
      GetDialogItemAsControl((DialogPtr) theDialog,gFirstAppendedItemNo + 3,&controlHdl);
       SetControlValue(controlHdl,0);

      GetDialogItemAsControl((DialogPtr) theDialog,itemHit,&controlHdl);
       SetControlValue(controlHdl,1);
     
       if(localizedItemNo == iRadioButton10pt)
         gFontSize = 10;
       else if(localizedItemNo == iRadioButton12pt)
         gFontSize = 12;
       else if(localizedItemNo == iRadioButton14pt)
         gFontSize = 14;
     }
     else if(localizedItemNo == iCheckboxFracWidths)
     {
       GetDialogItemAsControl((DialogPtr) theDialog,gFirstAppendedItemNo +
                              4,&controlHdl);
       SetControlValue(controlHdl,!GetControlValue(controlHdl));
      SetFractEnable(GetControlValue(controlHdl));
    }
  }
  else
  {
    CallPItemProc(gOldItemEvaluateFunctionUPP,(DialogPtr) theDialog,itemHit);
  }
}

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

pascal Boolean  eventFilter(DialogPtr dialogPtr,EventRecord *eventStrucPtr,
                            SInt16 *itemHit)
{
  Boolean  handledEvent;
  GrafPtr  oldPort;

  handledEvent = false;

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

    handledEvent = StdFilterProc(dialogPtr,eventStrucPtr,itemHit);

    SetPort(oldPort);
  }

  return(handledEvent);
}

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

Demonstration Program Comments

When the program is run, the user should:

*   Choose Page Setup... from the File menu, make changes in the style dialog, and
    observe the resulting contents of the main fields of the Tprint, TPrJob, TPrStyl,
    and TPrInfo structures in the window.

*   Choose Print... from the File menu, note the items added to the job dialog box,
    select the desired font, font size, and fractional widths setting using these items,
    and print the simulated document.

The user should print the simulated document several times using different page size,
scaling, and orientation settings in the style dialog, and occasionally limiting the
printout to one page only by changing the page range settings in the job dialog.

Printing.h

#define

Constants are established for the resource IDs of the 'PICT' and 'TEXT' resources used
for the printing demonstration, and for the 'DITL' resource containing the items to be
appended to the job dialog.  Five constants are established for the item numbers of the
items in the 'DITL' resource.  kMargin is used to set the margins for the printed page.

Printing.c

Global Variables

gTPrintHdl will be assigned a handle to a TPrint structure.  gWindowPtr will be assigned
a pointer to the window.  gDone controls the exit from the main loop and thus program
termination.  

gPrintRecordInited will be set to true when a TPrint structure has been created and
initialised.  gInhibitPrintRecordsInfo is a flag which will prevent the display of
information in the window in certain circumstances.  gEditRecHdl will be assigned a
handle to a TextEdit edit structure.  gTextHdl will be assigned a handle to the text used
for printing.  gPictureHdl will be assigned a handle to the picture used for printing.

gFontNumber will be assigned a font number derived from the font name obtained via
user-selection from the Font pop-up menu appended by the application to the job dialog. 
gFontSize will be assigned a font size obtained via user-selection from the appended
radio buttons.

gTPrDlgStructurePtr will be assigned a pointer to a TPrDlg structure. The next four
globals will be assigned universal procedure pointers relating to the initialisation
function, item evaluation function, and event filter function associated with the
appending of items to the job dialog.

main

After the window is opened, GetResource is called to loads the 'TEXT' resource, and
GetPicture is used to load the 'PICT' resource, used in the printing demonstration.
 
Note that, in this program, error handling of all errors other than Printing Manager
errors is somewhat rudimentary.  The program simply exits.

doUpdate

doUpdate calls the application-defined function which draws information in the window. 
Note that this function will be called from the application defined event-filter function
for the modified job dialog box.

doMenuChoice

If the user chooses Page Setup... from the File menu, the application-defined function
doPrStyleDialog is called.  If the user chooses Print... from the File menu, the
application-defined function doPrinting is called.

doPrinting

doPrinting contains the printing loop.  It supports printers using deferred printing. 
However, it does not use a saved TPrint structure (but rather creates one for the print
job), and does not use a custom status dialog box and associated idle function.  Also, it
does not unload unneeded code segments at the beginning.  (Unloading code segments
applies only to 680x0 code.  See Chapter 23 - Miscellany.)

The first line saves the pointer to the current graphics port.  The call to PrOpen opens
the Printing Manager, together with the current printer driver.

If the global variable gPrintStructureInited indicates that a TPrint structure has not
already been created, the application-defined function doCreatePrintRecord is called to
create and initialise a TPrint structure.

Before the printing loop is entered, and because the application will be modifying the
standard job dialog box, PrJobInit is called to get a pointer to the TPrDlg structure for
the dialog box defined in the resource file for the current printer driver.  This pointer
will be used in the initialisation function which appends items to the standard job
dialog.

If no errors have occurred, routine descriptors are created for the functions associated
with the modification of the standard job dialog box, and PrDlgMain is called to display
the dialog.  A universal procedure pointer to the initialisation function which appends
items to the standard job dialog is passed in the last parameter of this call.  When the
user dismisses the dialog, the routine descriptors are disposed of.

Note that, if an unmodified job dialog is required, the call to PrJobInit, together with
the calls to create and dispose of the routine descriptors, should be deleted.  In
addition, the call to PrDlgMain should be replaced by:

     userClickedOK = PrJobDialog(gTPrintHdl);

Note also that, when the Print button is hit, PrDlgMain calls PrValidate to ensure that
the contents of the TPrint structure are compatible with the printer driver for the
current printer.

If false is returned by the call to PrDlgMain (that is, the user clicked the Cancel
button), the printing loop is bypassed.  Otherwise, the first actions are to call an
application-defined function to calculate the number of pages, and then retrieve the
number of copies, the first page and the last page from the relevant fields of the TPrJob
structure.

Since the only information that should be preserved between separate printings of the
same document is that obtained via the style dialog box, the fields of the TPrJob
structure which store the first and last page numbers are set back to 1 and iPrPgMax
(9999) respectively before proceeding further.

If the last page number specified by the user exceeds the total number of pages in the
document, the variable holding the last page value is set to the actual number of pages.

The copies loop is then entered, followed by the nested pages loop.  The maximum number
of pages that can be printed at a time is represented by the constant iPFMaxPgs (128). 
The first two lines in the pages loop determine whether this is the first or the 129th
(or 257th, etc.) time around the pages loop.  If, for example, it is the 129th (that is,
128 pages have been printed), PrCloseDoc is called to close the printing graphics port
and, if the printer driver is using deferred printing, PrPicFile is called to send the
spool file to the printer.  If this is either the first page of all or the first page
after the first 128 (or 257, etc.) have been printed, PrOpenDoc is called to initialise a
new printing graphics port and makes it the current port.

For each page, PrOpenPage re-initialises the printing graphics port, the
application-defined function doDrawPage is called to draw that page's contents in the
printing graphics port, and PrClosePage wraps up the printing of the current page.

When all pages have been printed, PrCloseDoc closes the printing graphics port and, if
the printer driver is using deferred printing, PrPicFile sends the spool file to the
printer.

When all copies have been printed (or if control fell through as a result of an error),
PrClose releases all memory associated with the Printing Manager (except the printer
driver), and the result of the preceding call to PrError is then examined.  If an error
occurred, and provided that error was not the error that is reported when the user (or
the application) requests an abort, a Caution alert is displayed advising the user of the
error and error number.

Finally, the saved graphics port is restored.

doCreatePrintRecord

doCreatePrintRecord creates and initialises a TPrint structure.

Memory is allocated by the call to NewHandleClear.  If the call to allocate memory is
successful, PrintDefault initialises the TPrint structure to the system standard
settings.  If this call is successful, the global variable (gPrintStructureInited) which
indicates whether an initialised TPrint structure exists is set to true.  The result of
the PrError call is returned to the calling function.

If the call to allocate memory is not successful, the last line simply closes down the
program.

doPrStyleDialog

doPrStyleDialog is called when the user chooses Page Setup... from the File menu.

The call to PrOpen opens the Printing Manager and printer driver.  If the call is
successful, the second if statement determines whether a TPrint structure currently
exists.  If not, doCreatePrintRecord is called to create a new TPrint structure.  If
doCreatePrintRecord does not return noErr, a Stop alert is invoked.  

PrStylDialog opens the style dialog box.

If the call to PrOpen was not successful, a Caution alert is invoked. 

PrClose closes the Printing Manager (but not the printer driver), releasing the
associated memory.

doCalcNumberOfPages

doCalcNumberOfPages is called by doPrinting to calculate the number of pages in the
(simulated) document.

The simulated document is provided by a 'TEXT' resource, which will be inserted into a
TextEdit monostyled edit structure.  TextEdit is not addressed until Chapter 19 - Text
and TextEdit; however, to facilitate an understanding of what is to follow, it is
sufficient at this stage to understand that a monostyled edit structure contains the
following fields:

   destRect    The destination rectangle into which text is drawn.  The bottom of the
               destination rectangle can extend to accommodate the end of the text.  In 
               other words, you can think of the destination rectangle as bottomless.

   viewRect    The rectangle within which text is actually displayed.

   hText       A handle to the text.

   lineHeight  The vertical spacing, in pixels, of the lines of text.

   nLines      The total number of lines of text.

   linestarts  An array with a number of elements corresponding to the number of lines
               of text.  Each element contains the offset of the first character in 
               each line.

EraseRect erases the window preparatory to the simulated document being drawn in the
window.

The next block establishes a rectangle equal to the received page rectangle less 180
pixels in width and 270 pixels in height.  (As will be seen, this is the same size as the
rectangle to be used in the drawing of each page in the printing graphics port.)  The
call to OffsetRect simply offsets this rectangle so that, when the document is drawn in
the window, the top and right margins will be reduced to five pixels.

The calls to TextFont and TextSize set the window's font and font size to those
chosen/selected by the user at the pop-up menu and radio buttons added by the application
to the standard job dialog box.

TENew creates a new monostyled edit structure with the previously defined rectangle
passed in both the destination rectangle parameter and the view rectangle parameters. 
TEInsert inserts the previously loaded 'TEXT' resource into the edit structure.  The
hText field of the edit structure is now a handle to that text.  The call to TEInsert
also causes the text to be drawn in the window.  (A 'TEXT' resource, rather than a 'TEXT'
file, is used in this demonstration simply to keep that part of the source code that is
not related to printing per se to a minimum.)

The matter of the actual calculation of the number of pages now follows.  The first line
in this block gets the height of the previously defined rectangle.  The next line
calculates how many lines of text will fit into that height.  The third line then
calculates the total number of rectangles (and thus the number of pages) required to
accommodate the whole of the text.

Before the calculated number of pages is returned to the calling function, the previously
loaded picture is drawn at the top of the destination rectangle.  This latter is simply
to display the full contents of the top of the simulated document in the window.  (Space
for the picture is accounted for by the fact that the first 11 lines in the 'TEXT'
resource are carriage returns.)

The edit structure is retained because it will be used in the following function.

doDrawPage

doDrawPage is called by doPrinting to draw a specified page in the printing graphics
port.

The first block establishes a rectangle equal to the received page rectangle less 180
pixels in width and 270 pixels in height.  This smaller rectangle is centred on the page
rectangle both laterally and vertically.

The next block calculates the number of lines of text that will fit into the height of
that rectangle, and gets the total number of lines in the monostyled edit structure
created in the function doCalcNumberOfPages.

The next block sets the printing graphics port's font and font size to the same font and
size used to calculate the number of pages.

TENew creates a new monostyled edit structure with the previously defined rectangle
passed in both the destination rectangle parameter and the view rectangle parameter.  The
following line gets a handle to the text in the monostyled edit structure created in the
function doCalcNumberOfPages.

In the next block, the first line gets the starting offset, that is, the offset from the
first character in the block of text to the first character in the first line of text for
the specified page number.  The next four lines get the ending offset, that is, the
offset to the last character in the last line of text for the specified page.  

Using these offsets, TEInsert then inserts the text for the page into the newly created
edit structure, an action which causes that text to be drawn in the printing graphics
port.

If this is the first page, the next block draws the previously loaded picture at the top
left of the previously defined rectangle.

The last three lines draw the page number at the bottom left of that rectangle.

doDrawPageOrientation

doDrawPageOrientation ascertains the page orientation selected by the user in the style
dialog box and prints it in the window.  It gets a value representing the orientation via
a call to the application-defined function doGetPageOrientation.

doGetPageOrientation

doGetPageOrientation uses PrGeneral to establish the page orientation setting to be used
for printing.  

A TGetRotnBlk structure is used when PrGeneral is used to determine whether landscape
orientation has been specified.

If the current printer driver supports PrGeneral (determined by the call to
doIsPrGeneralThere), the iopCode field of the TGetRotnBlk structure is assigned the
opcode getRtnOp and the hPrint field is assigned the handle to the TPrintRecord. 
PrGeneral is then called with the address of the TGetRotnBlk structure as its argument.

Following the call, the fLandscape field of the TGetRotnBlk structure will contain true
if landscape orientation has been selected.  In this case (and assuming no errors), a
value representing landscape orientation is returned to the calling function, otherwise a
value representing portrait orientation is returned.

If the current printer driver does not support PrGeneral, a value representing this
situation is returned.

doIsPrGeneralThere

doIsPrGeneralThere is called by doGetPageOrientation to determine whether the current
printer driver supports PrGeneral.  The procedure is similar to that in
doGetPageOrientation, except that here we are interested only in the error code generated
by a call to PrGeneral.  If that error is the error represented by the constant
resNotFound (-192), PrGeneral is not supported and false is returned, otherwise true is
returned.

Note that PrSetError is used to set the value in the low-memory global PrintErr to noErr
in case PrGeneral generated an error code other than noErr.  PrintErr holds the most
recent Printing Manager error code and, since an actual printing error did not occur, it
is necessary to ensure that PrintErr reflects that fact.

doPrintRecordsInfo and doDrawRectStrings

doPrintRecordsInfo extracts information from the TPrint, TPrInfo, TPrStyl and TPrJob
structures and prints it in the window if a TPrint structure has been initialised and the
global variable gInhibitPrintStructuresInfo contains false.  doDrawRectStrings supports
doPrintRecords.

doErrorAlert

doPrintError is called from doPrinting and doPrStyleDialog if an error code is generated
following a call to a Printing Manager function.  Depending on the nature of the error,
either a Stop alert or a Caution alert is displayed, each containing the reported error
code.  In the case of a Stop alert, the program terminates when the user clicks the OK
button.

JobDialogAppend.c

initialisationFunction

Recall that, in the function doPrinting, a universal procedure pointer to
initialisationFunction was passed in the pDlgInit parameter of the PrDlgMain call. 
PrDialogMain thus calls this function to append the DITL to the end of the job dialog's
item list, and to set up the pItemProc (item evaluation) and pFilterProc (event filter
function) fields of the TPrDlg structure.

The DITL to be appended contains the following items:

*   A pop-up menu button (AddResMenu variant) for font selection. 'FONT' is the
    specified resource type.

*   Three radio buttons for font size selection.

*   A checkbox for selecting fractional widths on or off.

*   A primary group box (text title variant).

At the first line, the application-defined function doAppendTheDITL is called to append
the items.  Amongst other things, doAppendTheDITL assigns a value to the global variable
gFirstAppendedItemNo.  This value represents the item number in the job dialog's item
list of the first appended item (the pop-up menu button).

The next three blocks make the font pop-up menu WYSIWYG (using essentially the same code
as was used in the demonstration program Menus2) check the second radio button and set
the global variable holding the current font size to 12pt, and uncheck the checkbox and
set fractional widths to off.

The printer driver's item evaluation function will be called upon by the
application-defined item evaluation function to handle mouse-downs in the job dialog's
standard items.  Accordingly, the universal procedure pointer in the TPrDlg structure's
pItemProc field is saved for future use.  The universal procedure pointer to the
application-defined item evaluation function is then assigned to the pItemProc field.

As is required by PrDlgMain, the last line returns a pointer to the modified TPrDlg
structure.

doAppendTheDITL

doAppendTheDITL is called by initialisationFunction to append the new items to the job
dialog.

GetResource loads the specified 'DITL' resource and gets a handle to it.  CountDITL
counts the current number of items in the job dialog.  AppendDITL then appends the new
items at the bottom of the dialog, causing the dialog box to expand downwards to
accommodate them.

Note:  If the currently chosen printer is the LaserWriter 8 (driver version 8.4 and
later), the result of the AppendDITL call will be somewhat different in that the items
will be added to a pane and the name of the application will be inserted into the menu of
a pop-up group box.  (See Fig 5.)

(Effect of AppendDITL - LaserWriter 8)

The global variable gFirstAppendedItemNo is then assigned the item number in the new job
dialog item list of the first appended item (the pop-up menu button).

itemEvaluationFunction

itemEvaluationFunction handles item hits in the job dialog.  The item number of the item
hit is received in the second parameter.

At the first line, the item number of the item hit is "localised".  This means that, for
example, the localised item number of the pop-up menu button will be 1.  In other words,
if the localised item number is greater than 0, it will be the item number of one of the
appended items; otherwise, it will be the item number of one of the job dialog's standard
items.

If the localised item number is greater than 0, and if it is the localised item number
for the pop-up menu button, the control's value (that is, the menu item number) is
retrieved. GetMenuItemText is called to get the text of the menu item, and GetFNum is
called to get the font number for this font and assign it to the relevant global
variable.

If the localised item number is the localised item number for one of the radio buttons,
all radio buttons are unchecked, the radio button hit is checked, and the global variable
which holds the current text size is assigned the appropriate value.  If the localised
item number is the localised item number for the checkbox, the current value of that
control is flipped and SetFractEnable is called to set fractional widths on or off as
appropriate.

If the localised item number is 0 or less, the item must be one of the job dialog's
standard items.  In this case, the printer driver's item evaluation function is called
upon to handle the item hit.

eventFilter

eventFilter is identical to the custom event filter for modal dialogs introduced at
Chapter 8 - Dialogs and Alerts.  The use of a the event filter is optional.  Its use in
this program simply allows the dialog, together with windows belonging to background
applications, to receive update events.
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

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 »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.