{************************************************}
{                                                }
{   Turbo Pascal for Windows                     }
{   Tips & Techniques Demo Program               }
{   Copyright (c) 1991 by Borland International  }
{                                                }
{************************************************}

unit WinPrint;

{$R PRINTER}

interface

uses WinTypes, WinProcs, WObjects, Strings;

type

{ TComboXferRec }
{ The transfer buffer used for the ComboBox in the TPrinterInfo method
  SelectPrinter.  The fields, Strings and Selection, are set up in the
  TPrinterInfo constructor Init.  The routine GetCurrentPrinter is used
  to find current printing device which is placed in Selection.  And the
  routine GetPrinterTypes is used to fill out the Strings field.}

  TComboXferRec = record
    Strings: PStrCollection;
    Selection: array[0..80] of Char;
  end;

{ TAbortDialog }
{ A descendant of TDialog used for the Abort Dialog seen when printing is
  in progress. The AbortDialog is installed as a data field of TPrinterInfo
  and is initialized and displayed in its StartDoc method. The EndDoc
  method will Close the dialog if necessary.}

  PAbortDialog = ^TAbortDialog;
  TAbortDialog = object(TDlgWindow)
   procedure SetUpWindow; virtual;
   procedure WMCommand(var Msg: TMessage);
     virtual wm_First + wm_Command;
  end;

{ TPrinterInfo }
{ The controlling object for printing.  It is intended that this object be
  initialized as a data field of a TWindow or TApplication descendant. This
  printing object must be used OWL based applications. The data fields are
  not supposed to be used directly but may need to be accessed in special
  situations.  PrintDC and Error are the two most likely to be used without
  a specific method call.  The description of the data fields are as
  follows.

  -AbortDialog holds a pointer to the abort dialog when it valid.  It is
  valid only after a call to the method StartDoc and before the call to the
  method EndDoc.

  -AbortCallBackProc holds the address of the Abort Dialog's callback
  function.  It's definition is found in the function AbortCallBack in the
  implementation section of this unit.

  -SelectDialog is a pointer to the dialog used when selecting the current
  printer. To be used when overriding the function of the SelectPrinter
  method.

  -SelectInfo is the transfer record used in SelectDialog.  Holds
  descriptions of all printers available and the currently selected printer.

  -Driver, PrinterType, Port are null terminated strings holding information
  relevant to the current printer.

  -DriverHandle is a handle to the library of the current printer driver. It
  is setup in Init constructor and is freed in the Done destructor.  It is
  used for setting up the DeviceMode configuration call.

  -PrintDC is the device control established for printing. It is created by
  the StartDoc method and valid until the EndDoc method call. May be
  accessed directly or by the GetPrinterDC method call.

  -Error holds the results of printer escape calls.  If an error occurs, the
  result is placed here.  Is tested to determine if further printing output
  is appropriate.

  -ExtDeviceMode holds the ExtDeviceMode procedure used for retrieving,
  installing, and prompting for printing configurations.

  -DeviceModeVar holds the DeviceMode procedure used for prompting the
  user for printer configurations.
}

  PPrinterInfo = ^TPrinterInfo;
  TPrinterInfo = object
    AbortDialog: PAbortDialog;
    AbortCallBackProc: TFarProc;
    SelectDialog: PDialog;
    SelectInfo: TComboXferRec;
    Driver,
    PrinterType,
    Port: PChar;
    DriverHandle: THandle;
    PrintDC: HDC;
    Error: Integer;
    ExtDeviceMode: TExtDeviceMode;
    DeviceModeVar: TDeviceMode;
    RasterCaps: integer;
    constructor Init;
    destructor Done;
    procedure SelectPrinter; virtual;
    function GetPrinterDC: HDC;
    procedure DeviceMode;
    function BitMapCapable: boolean;
    function BandingRequired: boolean;
    procedure StartDoc(Name: PChar); virtual;
    procedure NewFrame; virtual;
    procedure NextBand(var R:TRect); virtual;
    procedure EndDoc; virtual;
  end;


var
  PrinterAbort: Boolean;
{ Holds true when the user has aborted printing. }

implementation

const
  id_ComboBox = 101;
{ ID for the ComboBox used for Selecting the current printer }

var
  AbortWindow: HWnd;
{ Window handle for the Abort Dialog.  It is used by the
  AbortCallBackProc.}

function GetItem(var S: PChar): PChar;
{ Retrieves comma separated data from a null terminated string. It
  returns the first data item and advances the pointer S to the next
  data item in the string.}
var
  P: PChar;
  I: Integer;

begin
  I:=0;
  while (S[I]<>',') and (S[I]<>#0) do
    inc(I);
  S[I]:=#0;
  GetMem(P, Strlen(S)+1);
  StrCopy(P,S);
  GetItem:=P;
  if S[0]<>#0 then S:=@S[I+1];
end;

procedure GetPrinterTypes(var PrinterTypes: PStrCollection);
{ Retrieves all the device types from the WIN.INI and places this
  information into the PStrCollection parameter.}
var
  Buffer, BufferItem: PChar;
  Item: PChar;
  Count, I: Integer;

begin
  New(PrinterTypes, init(5,1));
  GetMem(Buffer, 1024);
  Count:=GetProfileString('devices', nil, ',,', Buffer, 1024);
  BufferItem:=Buffer;
  I:=0;
  while I<Count do
  begin
    GetMem(Item, StrLen(BufferItem)+1);
    StrCopy(Item, BufferItem);
    PrinterTypes^.Insert(Item);
    while (BufferItem[i]<>#0) and (I<Count) do
      inc(I);
    inc(I);
    if BufferItem[I]=#0 then I:=Count;
    if I<Count then
    begin
      BufferItem:=@BufferItem[I];
      Count:=Count-I;
      I:=0;
    end;
  end;
  FreeMem(Buffer, 1024);
end;

procedure GetCurrentPrinter(var Driver, PrinterType, Port: PChar);
{ Retrieves the current printing device information from the WIN.INI
  file.}
var
  ProfileInfo, CurrentItem: PChar;
begin
  GetMem(ProfileInfo, 80+1);
  GetProfileString('windows', 'device', ',,', ProfileInfo, 80);
  CurrentItem:=ProfileInfo;
  PrinterType:=GetItem(CurrentItem);
  Driver:=GetItem(CurrentItem);
  Port:=GetItem(CurrentItem);
  FreeMem(ProfileInfo, 80+1);
end;

procedure GetPrinter(PrinterType: PChar; var Driver, Port: PChar);
{ Given a PrinterType string, this procedure returns the appropriate
  driver and port information.}

var
  ProfileInfo, CurrentItem: PChar;

begin
  GetMem(ProfileInfo, 80+1);
  GetProfileString('devices', PrinterType, ',', ProfileInfo, 80);
  CurrentItem:=ProfileInfo;
  Driver:=GetItem(CurrentItem);
  Port:=GetItem(CurrentItem);
end;

procedure TAbortDialog.SetUpWindow;
{ Initializes PrinterAbort and AbortWindow. Then set the focus to the
  AbortDialog.}
begin
  PrinterAbort:=false;
  SetFocus(HWindow);
  AbortWindow:=HWindow;
end;

procedure TAbortDialog.WMCommand(var Msg: TMessage);
{ If any command messages occur, a user abort has taken place.  Normally,
  this will include pressing ENTER, ESCAPE, the SPACEBAR  or clicking the
  mouse on the Abort Dialog's Escape button.}
begin
  PrinterAbort:=true;
end;

function AbortCallBack(DC: HDC; Code: Integer): Bool; export;
{ While printing is taking place, checks to see if PrinterAbort is
  true.  Otherwise messages are passed on.}
var
  Msg: TMsg;
begin
  while (not PrinterAbort) and PeekMessage(Msg, 0, 0, 0, pm_Remove) do
  if not IsDialogMessage(AbortWindow, Msg) then
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
  if PrinterAbort then AbortCallBack:=false else AbortCallBack:=true;
end;

constructor TPrinterInfo.Init;
{ Gets the current printer information (Type, Driver, & Port) and
  the printer types currently available.  Then retrieves the
  ExtDeviceMode and DeviceModeVar address from the current printer's
  library.}
var
  I: Integer;
  FullDriverName: PChar;
  P: TFarProc;

begin
  GetCurrentPrinter(Driver, PrinterType, Port);
  for I:= 0 to StrLen(PrinterType) do
    SelectInfo.Selection[I]:=PrinterType[I];
  GetPrinterTypes(SelectInfo.Strings);

  GetMem(FullDriverName, 12+1);
  StrLCat(StrCopy(FullDriverName, Driver), '.DRV', 12);
  DriverHandle:=LoadLibrary(FullDriverName);
  FreeMem(FullDriverName, 12+1);

  P:=GetProcAddress(DriverHandle, 'ExtDeviceMode');
  ExtDeviceMode:=TExtDeviceMode(P);
  P:=GetProcAddress(DriverHandle, 'DeviceMode');
  DeviceModeVar:=TDeviceMode(P);
  PrintDC:=0;
end;

destructor TPrinterInfo.Done;
{ Frees up the library taken in the constructor Init.}
begin
  FreeLibrary(DriverHandle);
end;

procedure TPrinterInfo.SelectPrinter;
{ Displays a Printer Select dialog called PISELECT and changes the
  current printer information as is done in Init.}
var
  FullDriverName: PChar;
  P: TFarProc;
  ComboBox: PComboBox;

begin
  new(SelectDialog, Init(Application^.MainWindow,
    'PISELECT'));
  New(ComboBox, InitResource(SelectDialog, id_ComboBox, 80));

  SelectDialog^.TransferBuffer:=@SelectInfo;
  if Application^.ExecDialog(SelectDialog) = id_Ok then
  begin
    FreeLibrary(DriverHandle);
    if PrintDC<>0 then DeleteDC(PrintDC);
    FreeMem(PrinterType, StrLen(PrinterType)+1);
    GetMem(PrinterType, StrLen(@SelectInfo.Selection)+1);

    StrCopy(PrinterType, @SelectInfo.Selection);

    FreeMem(Driver, StrLen(Driver)+1);
    FreeMem(Port, StrLen(Port)+1);
    GetPrinter(PrinterType, Driver, Port);

    GetMem(FullDriverName, 12+1);
    StrLCat(StrCopy(FullDriverName, Driver), '.DRV', 12);
    DriverHandle:=LoadLibrary(FullDriverName);
    FreeMem(FullDriverName, 12+1);

    P:=GetProcAddress(DriverHandle, 'ExtDeviceMode');
    ExtDeviceMode:=TExtDeviceMode(P);
    P:=GetProcAddress(DriverHandle, 'DeviceMode');
    DeviceModeVar:=TDeviceMode(P);
  end;
end;

function TPrinterInfo.GetPrinterDC: HDC;
{ Retrieves the Device control associated with the printer.  May only be
  called after a call to the StartDoc method. }
begin
  GetPrinterDC:=PrintDC;
end;

procedure TPrinterInfo.StartDoc(Name: PChar);
{ Called immediately before printing is to begin.  Establishes the
  device control.  Sets up the Abort Dialog. And send the STARTDOC
  escape call.}
begin
  Error:=0;
  PrintDC:=CreateDC(Driver, PrinterType, Port, nil);
  if LowMemory then
    AbortDialog:=Nil
  else
  begin
    new(AbortDialog, Init(Application^.MainWindow, 'PIABORT'));
    AbortDialog^.Create;
  end;
  if AbortDialog<>Nil then
  begin
    AbortCallBackProc:=MakeProcInstance(@AbortCallBack, HInstance);
    Escape(PrintDC, SETABORTPROC, 0, AbortCallBackProc, nil);
  end;
  RasterCaps:=GetDeviceCaps(PrintDC, WINTYPES.RASTERCAPS);
  Error:=Escape(PrintDC, WINTYPES.STARTDOC, StrLen(Name), Name, nil);
end;

procedure TPrinterInfo.NewFrame;
{ Sends the NEWFRAME escape call and performs appropriate error
  checking.}
begin
  if Error>=0 then
    Error:=Escape(PrintDC, WINTYPES.NEWFRAME, 0, nil, nil);
  if Error<0 then
    case Error of
      SP_ERROR: MessageBox(Application^.MainWindow^.HWindow,
        'General Printer Error', nil, mb_Ok or mb_IconStop);
      SP_OUTOFDISK: MessageBox(Application^.MainWindow^.HWindow,
        'No disk space for spooling', nil, mb_Ok or mb_IconStop);
      SP_OUTOFMEMORY: MessageBox(Application^.MainWindow^.HWindow,
        'No memory space for spooling', nil, mb_Ok or mb_IconStop);
      SP_USERABORT: MessageBox(Application^.MainWindow^.HWindow,
        'Printing Terminated by User', nil, mb_Ok or mb_IconStop);
    else
      MessageBox(Application^.MainWindow^.HWindow,
        'Printing Halted', nil, mb_OK or mb_IconStop);
    end;
end;

procedure TPrinterInfo.NextBand(var R:TRect);
{ When Bitmap banding is required, this routine returns the next
  rectangular region to be printed.  This method is not required but
  can speed up printing bitmaps.}
begin
  if Error>=0 then
    Error:=Escape(PrintDC, WINTYPES.NEXTBAND, 0, nil, @R);
  if Error<0 then
    case Error of
      SP_ERROR: MessageBox(Application^.MainWindow^.HWindow,
        'General Printer Error', nil, mb_Ok or mb_IconStop);
      SP_OUTOFDISK: MessageBox(Application^.MainWindow^.HWindow,
        'No disk space for spooling', nil, mb_Ok or mb_IconStop);
      SP_OUTOFMEMORY: MessageBox(Application^.MainWindow^.HWindow,
        'No memory space for spooling', nil, mb_Ok or mb_IconStop);
      SP_USERABORT: MessageBox(Application^.MainWindow^.HWindow,
        'Printing Terminated by User', nil, mb_Ok or mb_IconStop);
    else
      MessageBox(Application^.MainWindow^.HWindow,
        'Printing Halted', nil, mb_OK or mb_IconStop);
    end;
end;

procedure TPrinterInfo.EndDoc;
{ Sends the ENDDOC escape call and closes the Abort Dialog if no errors
  have occurred.}
begin
  if Error>=0 then
    Error:=Escape(PrintDC, WINTYPES.ENDDOC, 0, nil, nil);
  if Error>=0 then
  begin
    DeleteDC(PrintDC);
    if AbortDialog<>Nil then AbortDialog^.CloseWindow;
  end;
end;

procedure TPrinterInfo.DeviceMode;
{ Calls the printer driver's DeviceMode routine.  Normally displays a
  dialog allowing the user to change the printer's configuration.}
begin
  DeviceModeVar(Application^.MainWindow^.HWindow,
    DriverHandle, PrinterType, Port);
end;

function TPrinterInfo.BitMapCapable: boolean;
{ Returns true if the current printing device can handle bitmap
  graphics.}
begin
  BitMapCapable:=(RasterCaps and RC_BITBLT)<>0;
end;

function TPrinterInfo.BandingRequired: boolean;
{ Returns true if banding of bitmap images will enhance printing speed.}
begin
  BandingRequired:=(RasterCaps and RC_BANDING)<>0;
end;

end.

{ Here are the descriptions of the dialogs PIABORT and PISELECT found in
  the resources file PRINTER.RES

PIABORT DIALOG DISCARDABLE LOADONCALL PURE MOVEABLE 44, 46, 175, 78
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | 0x80L
CAPTION "Printing in Progress"
BEGIN
  CONTROL "Press Escape to Halt Printing" 101, "STATIC", WS_CHILD |
    WS_VISIBLE, 37, 17, 98, 12
  CONTROL "Escape" 102, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
    73, 49, 40, 13
END

PISELECT DIALOG DISCARDABLE LOADONCALL PURE MOVEABLE 44, 37, 145, 85
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | 0x80L
CAPTION "Select Printer"
BEGIN
  CONTROL "COMBOBOX" 101, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_VSCROLL |
    0x101L, 26, 11, 84, 43
  CONTROL "Ok" 1, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
    29, 61, 40, 12
  CONTROL "Cancel" 2, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
    86, 61, 40, 12
END
}
