/*
======================================================================
print.c

Ernie Wright  11 Jul 04
MSVC 4.0

Support for the Print button.
====================================================================== */

#include <windows.h>
#include <commdlg.h>
#include <string.h>
#include "qv.h"


INT_PTR CALLBACK PrintDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam );
BOOL CALLBACK AbortProc( HDC hdc, int code );


/* Global variables:  We need 'userabort' and 'hdlg' to be module-level
   because there's no other way to share them with the Win32 AbortProc()
   callback.  The PRINTDLG has program lifetime so that the user doesn't
   have to reset his printing preferences each time an image is printed,
   and it's module-level because Windows stores those preferences in
   memory that must be freed when the program exits. */

static BOOL userabort = FALSE;
static HWND hdlg = NULL;
static PRINTDLG pd = { 0 };



/*
======================================================================
printCleanup()

Free memory allocated by the Win32 function PrintDlg().

The first time PrintDlg() is called in printBitmap(), Windows creates
DEVMODE and/or DEVNAMES structures to hold internal and user settings
for the selected printer.  Handles to these are placed in the PRINTDLG
hDevMode and hDevNames fields and are preserved between calls to
printBitmap() so that the user won't have to, for example, reselect
landscape mode every time he prints.

This function will typically be called once, when the program exits.
====================================================================== */

void printCleanup( void )
{
   if ( pd.hDevMode ) {
      GlobalFree( pd.hDevMode );
      pd.hDevMode = NULL;
   }
   if ( pd.hDevNames ) {
      GlobalFree( pd.hDevNames );
      pd.hDevNames = NULL;
   }
}


/*
======================================================================
printBitmap()

Print the image to the selected printer.

INPUTS

   hwnd
      The window that will serve as the parent window for the Abort
      dialog.
   dib
      The bitmap header describing the image.
   bits
      The image pixels.

If the image is larger than the paper, it's scaled to fit.  Otherwise
it's centered on the page and printed at the size implied by the DIB's
biXPelsPerMeter and biYPelsPerMeter fields.  If those are 0, default
values equivalent to 100 DPI are used.

This is derived from Petzold 4th ed. and the Win32 SDK documentation
accompanying MSVC 4.0.  Note that both of these sources fail to call
AbortDoc() when the user cancels the print, and neither do proper
scaling (Petzold doesn't try, and the MSVC sample uses the wrong
device context).
====================================================================== */

void printBitmap( HWND hwnd, BITMAPINFOHEADER *dib, unsigned char *bits )
{
   FPoint pgPhysical, pgDPI, imPhysical, imDPI, scale;
   DOCINFO docinfo = { sizeof( DOCINFO ), "Image Viewer", NULL };
   HANDLE hinst;
   int x, y, w, h, i;
   BOOL ok;


   /* initialize the PRINTDLG */

   pd.lStructSize = sizeof( PRINTDLG );
   pd.hwndOwner   = hwnd;
   pd.nCopies     = 1;
   pd.Flags       = PD_ALLPAGES
                  | PD_HIDEPRINTTOFILE
                  | PD_NOPAGENUMS
                  | PD_NOSELECTION
                  | PD_RETURNDC;

   /* display the printer dialog, return if the user cancels */

   if ( !PrintDlg( &pd )) return;

   /* verify that the printer driver supports StretchDIBits() */

   if ( !( GetDeviceCaps( pd.hDC, RASTERCAPS ) & RC_STRETCHDIB )) {
      DeleteDC( pd.hDC );
      MessageBox( hwnd,
         "Printer doesn't support a required bitmap function.",
         "Printing Problem",
         MB_OK );
      return;
   }

   /* printer DPI and printable size of the paper, in inches */

   pgDPI.x = ( float ) GetDeviceCaps( pd.hDC, LOGPIXELSX );
   pgDPI.y = ( float ) GetDeviceCaps( pd.hDC, LOGPIXELSY );
   pgPhysical.x = GetDeviceCaps( pd.hDC, HORZRES ) / pgDPI.x;
   pgPhysical.y = GetDeviceCaps( pd.hDC, VERTRES ) / pgDPI.y;

   /* image DPI and size in inches */

   if ( dib->biXPelsPerMeter > 0 )
      imDPI.x = dib->biXPelsPerMeter / 39.37f;
   else
      imDPI.x = 72.0f;

   if ( dib->biYPelsPerMeter > 0 )
      imDPI.y = dib->biYPelsPerMeter / 39.37f;
   else
      imDPI.y = imDPI.x;

   imPhysical.x = dib->biWidth / imDPI.x;
   imPhysical.y = dib->biHeight / imDPI.y;

   /* if the image is bigger than the printable area, shrink the image */

   scale.x = imPhysical.x / pgPhysical.x;
   scale.y = imPhysical.y / pgPhysical.y;

   if ( scale.x > 1.0f || scale.y > 1.0f ) {
      if ( scale.x > scale.y ) {
         imPhysical.x /= scale.x;
         imPhysical.y /= scale.x;
      }
      else {
         imPhysical.x /= scale.y;
         imPhysical.y /= scale.y;
      }
   }

   /* arguments to StretchDIBits */

   x = ( int )( pgDPI.x * ( pgPhysical.x - imPhysical.x ) / 2 );
   y = ( int )( pgDPI.y * ( pgPhysical.y - imPhysical.y ) / 2 );
   w = ( int )( imPhysical.x * pgDPI.x );
   h = ( int )( imPhysical.y * pgDPI.y );

   /* disable the parent window and display the abort dialog */

   EnableWindow( hwnd, FALSE );
   userabort = FALSE;
   ok = TRUE;

   hinst = ( HANDLE ) GetWindowLongPtr( hwnd, GWLP_HINSTANCE );
   hdlg = CreateDialog( hinst, "printdlg", hwnd, PrintDlgProc );
   SetAbortProc( pd.hDC, AbortProc );

   /* print the image */

   if ( StartDoc( pd.hDC, &docinfo ) > 0 ) {
      for ( i = 0; i < pd.nCopies; i++ ) {
         if ( StartPage( pd.hDC ) < 0 ) {
            ok = FALSE;
            break;
         }

         StretchDIBits( pd.hDC, x, y, w, h, 0, 0, dib->biWidth, dib->biHeight,
            bits, ( BITMAPINFO * ) dib, 0, SRCCOPY );

         if ( EndPage( pd.hDC ) < 0 ) {
            ok = FALSE ;
            break;
         }

         if ( userabort ) {
            AbortDoc( pd.hDC );
            ok = FALSE;
            break;
         }
      }
   }
   else
      ok = FALSE;

   /* if successful, tell Windows we're finished */

   if ( ok ) EndDoc( pd.hDC );

   /* delete the printer device context */

   DeleteDC( pd.hDC );

   /* if not successful, tell the user */

   if ( !ok && !userabort )
      MessageBox( hwnd,
         "An error occurred during printing.",
         "Printing Problem",
         MB_OK );

   /* and we're done */

   EnableWindow( hwnd, TRUE );
   DestroyWindow( hdlg );
   hdlg = NULL;
}


/*
======================================================================
PrintDlgProc()

Callback for the printing abort dialog.  If the user presses the
Cancel button (the only one on the dialog), the global variable
'userabort' is set to TRUE.  During printing, printBitmap() tests
this value and turns the bus around if it's true.
====================================================================== */

static INT_PTR CALLBACK PrintDlgProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
   switch ( msg )
   {
      case WM_INITDIALOG:
         return TRUE;

      case WM_COMMAND:
         userabort = TRUE;
         return TRUE;
   }

   return FALSE;
}


/*
======================================================================
AbortProc()

Windows calls this callback about every 2 seconds, to give the user a
chance to cancel a print job while it's spooling.  If the user presses
the Cancel button on the print dialog, the resulting WM_COMMAND is
retrieved here by PeekMessage() and then sent by IsDialogMessage() to
PrintDlgProc(), which sets the global variable 'userabort' to TRUE.
====================================================================== */

static BOOL CALLBACK AbortProc( HDC hdc, int code )
{
   MSG msg;

   while( !userabort && PeekMessage( &msg, NULL, 0, 0, PM_REMOVE )) {
      if ( !hdlg || !IsDialogMessage( hdlg, &msg )) {
         TranslateMessage( &msg );
         DispatchMessage( &msg );
      }
   }

   return !userabort;
}
