
// ---------------------------------------------------------------------
//
// PictWnd.c - Picture Viewer - QuickTime for Windows
//
//             Version 1.0
//
//             (c) 1988-1992 Apple Computer, Inc. All Rights Reserved.
//
// ---------------------------------------------------------------------


// Includes
// --------
#include <Windows.H>   // Required by Windows
#include <commdlg.h>   // Required for PRINTDLG struct
#include <stdio.h>     // Required for sprintf routine
#include <stdlib.h>    // Required for fullpath routine
#include <sys\types.h> // Required for stat.h
#include <sys\stat.h>  // Required for _stat routine

#include <qtw.h>    // Interface to QuickTime
#include <qtole.h>  // Interface to qtole dll
#include "common.h" // Interface to common.c

#include "viewer.h"  // Interface to other *.c files
#include "viewer.hr" // Defines used in *.rc files
#include "picture.h" // Interface to child window processing

// Constants
// ---------
#define GETINFOPICTUREPROP    "Picture" // Used in initializing info dialog
#define SIZE_COLOR_TABLE     ( 256 * sizeof( RGBQUAD ))

// Message-Persistent Data
// -----------------------
static struct // Hungarian notation: g
  { WORD      wPictureCount;     // Used during enumeration for
                                 // duplication checking
    BOOL      bCreating;         //   "    "       "
    HWND      hwndFirstDup;      //   "    "       "
    HICON     hpictIcon;         // Picture wnd icon
    HPALETTE  hpalCurrentSystem; // Current system palette
    RECT      rcSelection;       // Selection rect

  } g;

// Macros
// ----------------------------
#define ISKEYDOWN( vKey )    ( GetKeyState( (int) (vKey) ) < 0 )
// Next macro is from windowsx.h
#define GlobalPtrHandle(lp)   \
               ( (HGLOBAL) LOWORD( GlobalHandle( SELECTOROF( lp ))))


// Exported callback function
// ----------------------------
BOOL __export CALLBACK GetInfoDlgProc     (HWND, UINT, WPARAM, LPARAM);
BOOL __export CALLBACK CheckDupEnumProc   (HWND, LPARAM);


// Internal Function Declarations
// ------------------------------
static LONG    NEAR ViewerPictureCreate      (HWND, LPARAM );
static LONG    NEAR ViewerEditCommands       (HWND, WPARAM, WORD);
static LONG    NEAR ViewerImageCommands      (HWND, WPARAM, WORD);
static LONG    NEAR PrintPicture             (HWND, LPPRINTDLG);
static VOID    NEAR FillPictureInfo          (HWND, NPPICTUREDATA);
static VOID    NEAR UpdateInfoFileName       (HWND, NPPICTUREDATA);
static VOID    NEAR UpdateInfoCurrentSize    (HWND, NPPICTUREDATA);
static LONG    NEAR InitializePopupMenus     (HWND, HMENU, int);
static LONG    NEAR PaintThePicture          (HWND);
static LONG    NEAR PaintTheIcon             (HWND);
static LONG    NEAR KeyDownMessage           (HWND, WPARAM, LPARAM);
static BOOL    NEAR BuildIconBitmap          (HWND, NPPICTUREDATA);

static BOOL    NEAR InitSelection            (HWND, POINT);
static VOID    NEAR MoveTheSelectRect        (HWND, POINT);
static VOID    NEAR EndTheSelection          (HWND);
static VOID    NEAR DrawSelectionRect        (HWND, HDC, NPPICTUREDATA);

static VOID    NEAR SetOptionsDefaults       (HWND, NPPICTUREDATA);
static VOID    NEAR PopulateOptionsStruct    (HWND, NPPICTUREDATA);


// Function: ViewerPictureWndProc - Viewer picture Window Procedure
// --------------------------------------------------------------------
// Parameters: As required by Microsoft Windows
//
// Returns:    Via DefMDIChildProc
// --------------------------------------------------------------------
LONG __export CALLBACK ViewerPictureWndProc
    (HWND hwndPicture, UINT message, WPARAM wParam, LPARAM lParam)

{
    NPPICTUREDATA    pPictureData;      // Temp -> to picture data struct
    HPICTUREDATA     hPictureData;      // Temp handle to picture data
    WNDENUMPROC      lpfnEnumPictures;  // -> enumeration proc
    NPPICTUREDATA    pFirstPictureData; // Temp -> to picture data
                                        // struct. Used during duplication
                                        // processing
    HDC              hdc;               // Window hdc
    HPALETTE         hpalSave;          // Prev palette
    POINT            ptCursor;          // Cursor position used in grow box
    LPNCCALCSIZE_PARAMS lpncsp;         // -> NCCCALCSIZE_PARAMS struct
    LPQTOLE_OLEDATA  lpOleData;         // -> ole data

    static BOOL      bDragging;  // Flag set when using grow box
    static BOOL      bSelecting; // Flag set when making a selection

    switch( message ) {
        case WM_CREATE:
            // Load picture frame icon used by all instances
            if( !g.hpictIcon )
                g.hpictIcon = LoadIcon( ViewerQueryResources(),
                MAKEINTRESOURCE( VIEWER_PICT_ICON ));
            return ViewerPictureCreate( hwndPicture , lParam );

        case WM_SIZE:
            // Turn off the max size limits set during
            // WM_NCLBUTTONDOWN processing
            if( bDragging ) {
                LimitTheDragSize( hwndPicture, bDragging = FALSE );
            }

            // This routine also updates scrolling parameters
            ResizeKids( hwndPicture, LOWORD( lParam ), HIWORD( lParam ));

            break; // Break to DefMDIChildProc


        // We don't use the CS_HREDRAW | CS_VREDRAW so we need to
        // tell when to repaint the entire picture. We repaint the whole
        // thing if the UL corner moves or either of the scroll
        // bars is enabled.
        case WM_NCCALCSIZE:
            if( wParam ) {
                lpncsp = (LPNCCALCSIZE_PARAMS) lParam;
                if( !( pPictureData =
                    (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )) ||
                    ( lpncsp->rgrc[0].left != lpncsp->rgrc[1].left ) ||
                    ( lpncsp->rgrc[0].top  != lpncsp->rgrc[1].top ) ||
                    IsWindowEnabled( pPictureData->spmsHScroll.hwnd ) ||
                    IsWindowEnabled( pPictureData->spmsVScroll.hwnd )) {
                    InvalidateRect( hwndPicture, NULL, FALSE );
                }
                else if( ( lpncsp->rgrc[0].right  > lpncsp->rgrc[1].right ) ||
                    ( lpncsp->rgrc[0].bottom > lpncsp->rgrc[1].bottom )) { // Paint out the grow box. At this point, the grow box rect
                                                                           // still contains the old rect since this message occurs
                                                                           // before the WM_SIZE message
                    InvalidateRect( hwndPicture,
                        &pPictureData->rcGrowBox, FALSE );
                }
            }

            break; // Break to DefMDIChildProc

        // This tells windows to treat the grow box like the
        // bottom right window frame
        case WM_NCHITTEST:
            if( !IsZoomed( hwndPicture )) {
                ptCursor = MAKEPOINT( lParam );
                ScreenToClient( hwndPicture, &ptCursor );
                if( IsInGrowBox( hwndPicture, ptCursor ))
                    return HTBOTTOMRIGHT;
            }

            break; // Break to DefMDIChildProc


        // Set the cursor to the arrow cursor in the grow box
        // Because of the WM_NCHITTEST processing we do, Windows
        // thinks it is in the corner border and will set the
        // cursor to the diagonal arrow.
        case WM_SETCURSOR:
            if( LOWORD( lParam ) == HTBOTTOMRIGHT ) {
                GetCursorPos( &ptCursor );
                ScreenToClient( hwndPicture, &ptCursor );
                if( IsInGrowBox( hwndPicture, ptCursor )) {
                    SetCursor( LoadCursor( NULL, IDC_ARROW ));
                    return 1L;
                }
            }
            break; // Break to DefMDIChildProc

        // Limit the window size so that the grow box resized window
        // stays inside the MDI client rect
        case WM_NCLBUTTONDOWN:
            if( wParam == HTBOTTOMRIGHT ) {
                ptCursor = MAKEPOINT( lParam );
                ScreenToClient( hwndPicture, &ptCursor );
                if( IsInGrowBox( hwndPicture, ptCursor )) {
                    LimitTheDragSize( hwndPicture, bDragging = TRUE );
                }
            }

            break; // Break to DefMDIChildProc


        // WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP are used to implement
        // grow box resizing of the frame window when the window is maximized
        case WM_LBUTTONDOWN:
            if( IsZoomed( hwndPicture ) &&
                !IsZoomed( ViewerQueryFrameWindow()) &&
                IsInGrowBox( hwndPicture, MAKEPOINT( lParam )) &&
                ( bDragging = InitMaxWndGrowBoxResize
                ( hwndPicture, MAKEPOINT( lParam )))) {
                SetCapture( hwndPicture );
            }
            else if( bSelecting = InitSelection
                ( hwndPicture, MAKEPOINT( lParam ))) {
                SetCapture( hwndPicture );
            }

            return 0L;

        case WM_MOUSEMOVE:
            if( bDragging && IsZoomed( hwndPicture )) {
                MoveTheFrameRect( hwndPicture, MAKEPOINT( lParam ));
            }
            else if( bSelecting ) {
                MoveTheSelectRect( hwndPicture, MAKEPOINT( lParam ));
            }

            return 0L;

        case WM_LBUTTONUP:
            if( bDragging && IsZoomed( hwndPicture )) {
                EndMaxWndGrowBoxResize( hwndPicture );
                bDragging = FALSE;
                ReleaseCapture();
            }
            else if( bSelecting ) {
                EndTheSelection( hwndPicture );
                bSelecting = FALSE;
                ReleaseCapture();
            }

            return 0L;


        case WM_NCACTIVATE:
            if( !( pPictureData =
                (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )))
                break; // Break to DefMDIChildProc

            if( (BOOL) wParam ) { // Set flag to disable the scroll bar processing. Although this
                                  // is only necessary in the case of palettized devices, it is
                                  // done in all cases in order to have a consistent user interface.
                                  // This disabling is necessary in order to avoid a problem with
                                  // simultaneous scrolling and palette switching that leaves a
                                  // portion of the picture incorrectly updated.
                                  // The visual effect of this disabling is that clicking on a
                                  // scroll bar will activate the window but will not cause the
                                  // scolling to actually occur.

                pPictureData->bScrollWaitForPaint = TRUE;

                // Realize palette if activating and is palettized device
                if( ViewerIsPalettized()) {
                    if( pPictureData->hpalPicture &&
                        ( hdc = GetDC( hwndPicture ))) {
                        hpalSave = SelectPalette
                            ( hdc, pPictureData->hpalPicture, 0 );

                        StoreCurrentSystemPalette
                            ( pPictureData->hpalPicture );
                        RealizePalette( hdc );

                        SelectPalette( hdc, hpalSave, 0 );
                        ReleaseDC( hwndPicture, hdc );

                        InvalidateRect( hwndPicture, NULL, FALSE );
                    }
                    else if( GetUpdateRect( hwndPicture, NULL, FALSE ))
                        InvalidateRect( hwndPicture, NULL, FALSE );
                }
                else { // Post message to reable scrolling because generally will
                       // not get a WM_PAINT message. Don't want to use
                       // InvalidateRect() because it will generate an unnessary
                       // repaint.
                    PostMessage( hwndPicture, WM_VIEWER_REENABLESCROLL, 0, 0L );
                }
            }

            break; // Break to DefMDIChildProc

        case WM_SYSCOMMAND:
            // Each picture needs an icon bitmap. We build it here
            // because the window must have first realized the palette.
            // This message appears after the WM_NCACTIVATE
            // message for this window and before the WM_NCACTIVATE
            // for the window that will eventually be activated.
            // This bitmap will be created and saved the first time
            // the window is iconized
            if( ( wParam == SC_MINIMIZE ) &&
                ( pPictureData =
                (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )) &&
                !pPictureData->hbitmapIcon && !pPictureData->lpDIBIconMem ) {
                BuildIconBitmap( hwndPicture, pPictureData );
            }

            break; // Break to DefMDIChildProc

        case WM_COMMAND:
            switch( wParam ) {
                case VIEWER_EDIT_COPYPICTURE: // edit menu popup
                case VIEWER_EDIT_OPTIONS:
                case VIEWER_EDIT_CANCELSEL:
                    return ViewerEditCommands
                        ( hwndPicture, wParam, HIWORD (lParam));

                case VIEWER_IMAGE_GETINFO: // image menu popup
                case VIEWER_IMAGE_HALFSIZE:
                case VIEWER_IMAGE_NORMALSIZE:
                case VIEWER_IMAGE_DOUBLESIZE:
                    return ViewerImageCommands
                        ( hwndPicture, wParam, HIWORD( lParam ));

                default:
                    break; // Break to DefMDIChildProc
            }

            break;

        case WM_KEYDOWN:
            return KeyDownMessage( hwndPicture, wParam, lParam );

        // WM_USER messages

        case WM_VIEWER_PRINTPICTURE:
            return PrintPicture( hwndPicture, (LPPRINTDLG) lParam );

        case WM_VIEWER_INITPOPUPS:
            return InitializePopupMenus
                ( hwndPicture, (HMENU) wParam, (int) LOWORD( lParam ));

        case WM_VIEWER_REENABLESCROLL:
            if( pPictureData = (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))
                pPictureData->bScrollWaitForPaint = FALSE;
            return 0L;

        // end WM_USER messages


        case WM_ERASEBKGND:
            // If there is a picture, let it deal with the background in
            // WM_PAINT
            return ((pPictureData =
                (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )) != NULL );

        case WM_HSCROLL:
            if( !( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 )) ||
                pPictureData->bScrollWaitForPaint ) {
                return 0L;
            }
            else {
                return ProcessHorzScroll
                    ( hwndPicture, (WORD) wParam, LOWORD( lParam ));
            }

        case WM_VSCROLL:
            if( !( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 )) ||
                pPictureData->bScrollWaitForPaint ) {
                return 0L;
            }
            else {
                return ProcessVertScroll
                    ( hwndPicture, (WORD) wParam, LOWORD( lParam ));
            }

        case WM_GETMINMAXINFO:
            SetMinMaxInfo( (MINMAXINFO FAR*) lParam );
            break; // Break to DefMDIChildProc

        case WM_QUERYDRAGICON:
            return MAKELONG( g.hpictIcon, 0 );

        case WM_PAINT:
            if( IsIconic( hwndPicture ))
                return PaintTheIcon( hwndPicture );
            else {
                if( PaintThePicture( hwndPicture )) { // Draw picture failed so close window
                    PostMessage( hwndPicture, WM_CLOSE, 0, 0L );
                }
                else if( pPictureData = (NPPICTUREDATA)
                    GetWindowWord( hwndPicture, 0 ))
                    pPictureData->bScrollWaitForPaint = FALSE;

                return 0L;
            }

        case WM_DESTROY:
            if( ( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 )) &&
                ( lpOleData = ViewerQueryOleData()) && lpOleData->lpqtoleServer ) {
                PopulateOptionsStruct( hwndPicture, pPictureData );
                if( !QTOLE_ClosingDocWnd( lpOleData,
                    (LPQTOLE_OPTIONS) &pPictureData->qtoleOptions ))
                    return 0L;
            }

            break; // break to DefMDIChildProc

        // Process WM_NCDESTROY instead of WM_DESTROY so that child controls
        // get WM_DESTROY messages before the stuff in here get executed
        case WM_NCDESTROY:
            if( !(pPictureData =
                (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )))
                break;

            if( pPictureData->hwndGetInfo )
                DestroyWindow( pPictureData->hwndGetInfo );

            if( pPictureData->phPicture != NULL)
                DisposePicture( pPictureData->phPicture );

            if( pPictureData->hpalPicture )
                DeleteObject( pPictureData->hpalPicture );

            if( pPictureData->hbitmapIcon )
                DeleteObject( pPictureData->hbitmapIcon );
            if( pPictureData->lpDIBIconMem ) {
                GlobalUnlock( GlobalPtrHandle( pPictureData->lpDIBIconMem ));
                GlobalFree( GlobalPtrHandle( pPictureData->lpDIBIconMem ));
            }

            // Last instance destroys icon stuff
            if( ViewerQueryNumPictures() <= 1 ) {
                if( g.hpictIcon ) {
                    DestroyIcon( g.hpictIcon );
                    g.hpictIcon = NULL;
                }
                g.hpalCurrentSystem = NULL;
                DestroyGrowBoxBitmap();
            }
            else // Check for duplicates
            {
                g.wPictureCount = 0;
                g.bCreating     = FALSE;
                g.hwndFirstDup  = NULL;
                if( lpfnEnumPictures = (WNDENUMPROC) MakeProcInstance
                    ( (FARPROC) CheckDupEnumProc, ViewerQueryInstance())) {
                    EnumChildWindows( ViewerQueryClientWindow(),
                        lpfnEnumPictures, MAKELPARAM( hwndPicture, 0 ));
                    FreeProcInstance( (FARPROC) lpfnEnumPictures );

                    // if no dups, eliminate :1 on first
                    // hwndFirstDup is set in CheckDupEnumProc
                    if( ( g.wPictureCount == 1 ) && g.hwndFirstDup &&
                        ( pFirstPictureData = (NPPICTUREDATA)
                        GetWindowWord( g.hwndFirstDup, 0 ))) {
                        pFirstPictureData->wDuplicationIndex = 0;
                        SetWindowText( g.hwndFirstDup,
                            pFirstPictureData->szPictureName );
                    }
                }
            }

            LocalUnlock( hPictureData =
                (HPICTUREDATA) LocalHandle( pPictureData ));
            LocalFree( hPictureData );
            SetWindowWord( hwndPicture, 0, 0 ); // Set this to 0 for any
                                                // subsequent messages

            SendMessage( ViewerQueryFrameWindow(),
                WM_VIEWER_PICTUREDELETED, (WPARAM) hwndPicture, 0L );

            break; // break to DefMDIChildProc

    }

    return DefMDIChildProc( hwndPicture, message, wParam, lParam );
}


// Function: ViewerPictureCreate - process WM_CREATE message
// --------------------------------------------------------------------
// Parameters: HWND       hwndPicture      Handle of picture window
//             LPARAM     lParam           lParam of WM_CREATE message
//
// Returns:    0L if OK, else returns -1L to kill app
// --------------------------------------------------------------------
static LONG NEAR ViewerPictureCreate( HWND hwndPicture, LPARAM lParam )

{
    NPPICTUREDATA      pPictureData;                    // Temp -> to picture data struct
    HPICTUREDATA       hPictureData;                    // Handle to picture data struct
    LPMDICREATESTRUCT  lpmdicreate;                     // MDI create struct
    struct _stat       statbuf;                         // File statictics struct
    char               szBuffer[MAX_PATH_LEN];          // Temp buffer
    DWORD              dwBytes;                         // File size
    OSErr              oserr;                           // Error return
    WORD               wIDString1;                      // Resource string id
    WORD               wIDString2;                      // Resource string id
    POINT              ptCorner;                        // Upper Left corner of pict wnd
    RECT               rcWindow;                        // Window rect
    int                nDiff;                           // Temp
    RECT               rcclientClient;                  // Client rect of MDI client wnd
    WORD               wWidth;                          // Width of window
    WORD               wHeight;                         // Height of window
    WNDENUMPROC        lpfnEnumPictures;                // -> to enumeration proc
    LPSTR              lpName;                          // Temp -> picture name
    PicFile            picPicFile;                      // Picture file handle
    HDC                hdc;                             // Window hdc
    HCURSOR            hcursorSave;                     // Prior cursor
    char               szCompressor[sizeof(DWORD) + 1]; // compressor type
    QTOLE_OPENWND      qtoleOpenWnd;                    // Ole open wnd struct
    LPQTOLE_OLEDATA    lpOleData;                       // -> ole data
    WORD               wZoomIndex;                      // Initial zoom index


    pPictureData = (NPPICTUREDATA) NULL;
    szBuffer[0] = '\0';
    hcursorSave = NULL;

    if( !(hPictureData = (HPICTUREDATA)
        LocalAlloc( LPTR, sizeof( PICTUREDATASTRUCT )))) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOMEMORY, NULL, MB_OK );
        goto Failed;
    }
    pPictureData = (NPPICTUREDATA) LocalLock( hPictureData );
    SetWindowWord( hwndPicture, 0, (WORD) pPictureData );

    // mdi struct filled before call to MDI create in LaunchPictureWnd
    lpmdicreate = (LPMDICREATESTRUCT)
        ((LPCREATESTRUCT) lParam)->lpCreateParams;

    lstrcpy( pPictureData->szPictureName, (LPSTR) lpmdicreate->szTitle );

    // Strip off and save extension so that name fits better
    // on title bar of window
    pPictureData->szPictureExt[0] = '\0';
    lpName = pPictureData->szPictureName;
    while( *lpName ) {
        if( *lpName == '.' ) {
            lstrcpy( pPictureData->szPictureExt, lpName );
            *lpName = '\0';
            SetWindowText( hwndPicture, pPictureData->szPictureName );
            break;
        }
        else
            lpName = AnsiNext( lpName );
    }

    // Open picture file and extract picture
    hcursorSave = SetCursor( LoadCursor( NULL, IDC_WAIT ));
    // Cache path
    lstrcpy( pPictureData->szPicturePath, (LPSTR) lpmdicreate->lParam );
    if( OpenPictureFile( pPictureData->szPicturePath,
        &picPicFile, OF_READ )) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOOPENFILE, VIEWER_STRING_CAPTION,
            MB_OK, (LPSTR) lpmdicreate->szTitle );
        goto Failed;
    }

    pPictureData->phPicture = GetPictureFromFile( picPicFile );
    ClosePictureFile( picPicFile );

    if( !pPictureData->phPicture ) {
        if( ( oserr = GetMoviesStickyError()) == noPictureInFile )
            wIDString1 = VIEWER_STRING_NOPICINFILE;
        else if( oserr == insufficientMemory )
            wIDString1 = VIEWER_STRING_NOMEMORY;
        else
            wIDString1 = VIEWER_STRING_NOGETPIC;

        ClearMoviesStickyError();

        CommonTellUser( ViewerQueryResources(),
            wIDString1, VIEWER_STRING_CAPTION,
            MB_OK, (LPSTR) lpmdicreate->szTitle );
        goto Failed;
    }

    // Check for duplicates. Fix up titles if necessary
    // Initialize globals used during enumeration
    g.wPictureCount = 1;
    g.bCreating =  TRUE;
    if( ( ViewerQueryNumPictures() > 0 ) &&
        ( lpfnEnumPictures = (WNDENUMPROC) MakeProcInstance
        ( (FARPROC) CheckDupEnumProc, ViewerQueryInstance()))) {
        EnumChildWindows( ViewerQueryClientWindow(),
            lpfnEnumPictures, MAKELPARAM( hwndPicture, 0 ));
        FreeProcInstance( (FARPROC) lpfnEnumPictures );

        // Fix up title if duplicate
        // Title of 1st dup is fixed up during enum
        if( g.wPictureCount > 1 ) {
            pPictureData->wDuplicationIndex = g.wPictureCount;
            wsprintf( szBuffer, "%s:%u",
                (LPSTR) pPictureData->szPictureName,
                pPictureData->wDuplicationIndex );
            SetWindowText( hwndPicture, szBuffer );
        }
    }

    // Get the palette for the picture if the monitor is palettized
    // A default palette is returned if the picture does not have a palette
    pPictureData->hpalPicture = NULL;
    if( ViewerIsPalettized() && ( hdc = GetDC( hwndPicture ))) {
        pPictureData->hpalPicture =
            GetPicturePalette( pPictureData->phPicture );
        ReleaseDC( hwndPicture, hdc );
    }


    // Get file size  Note: Need to switch to OEM char set.
    // Sizes are displayed in banner bar and in info dialog
    AnsiToOem( pPictureData->szPicturePath, szBuffer );
    if( (_stat( szBuffer, &statbuf )) == 0 ) {
        if( statbuf.st_size < 1000L ) {
            dwBytes = (DWORD) statbuf.st_size;
            wIDString1 = VIEWER_STRING_SIZEBYTES;
            wIDString2 = VIEWER_STRING_SIZEBYTESONDISK;
        }
        else {
            dwBytes =  (DWORD) ( statbuf.st_size / 1000L );
            wIDString1 = VIEWER_STRING_SIZEKBYTES;
            wIDString2 = VIEWER_STRING_SIZEKBYTESONDISK;
        }

        LoadString( ViewerQueryResources(),
            wIDString1, szBuffer, sizeof( szBuffer ));
        wsprintf( pPictureData->szFileSize, szBuffer, dwBytes );
        LoadString( ViewerQueryResources(),
            wIDString2, szBuffer, sizeof( szBuffer ));
        wsprintf( pPictureData->szFileSizeOnDisk, szBuffer, dwBytes );
    }


    pPictureData->idImageInfo.idSize = sizeof( ImageDescription );
    if( GetPictureInfo( pPictureData->phPicture, &pPictureData->idImageInfo )) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOINFO, VIEWER_STRING_CAPTION, MB_OK );
        goto Failed;
    }

    // Picture type
    if( pPictureData->idImageInfo.name[0] ) {
        lstrcpy( pPictureData->szPictType, pPictureData->idImageInfo.name );
    }
    else { // Compressor type is stored as a DWORD. i.e. 'jpeg'
        if( pPictureData->idImageInfo.CodecType ) {
            *((PDWORD) &szCompressor) = pPictureData->idImageInfo.CodecType;
            szCompressor[ sizeof(DWORD) ] = '\0';
        }

        pPictureData->szPictType[0] = '\0';
        // Spaces in "raw ", "rle " etc are necessary !!!
        if( !pPictureData->idImageInfo.CodecType ||
            !lstrcmpi( szCompressor, "raw " ))
            wIDString1 = VIEWER_STRING_CODEC_NONE;
        else if( !lstrcmpi( szCompressor, "jpeg" ))
            wIDString1 = VIEWER_STRING_CODEC_PHOTO;
        else if( !lstrcmpi( szCompressor, "rle " ))
            wIDString1 = VIEWER_STRING_CODEC_ANIMATION;
        else if( !lstrcmpi( szCompressor, "smc " ))
            wIDString1 = VIEWER_STRING_CODEC_GRAPHICS;
        else if( !lstrcmpi( szCompressor, "rpza" ))
            wIDString1 = VIEWER_STRING_CODEC_VIDEO;
        else if( !lstrcmpi( szCompressor, "cvid" ))
            wIDString1 = VIEWER_STRING_CODEC_CVID;
        else
            wIDString1 = VIEWER_STRING_CODEC_NONE;

        LoadString( ViewerQueryResources(), wIDString1,
            pPictureData->szPictType, sizeof( pPictureData->szPictType ));
    }

    // Adjust resolution for Mac wierdness
    if( ( pPictureData->idImageInfo.hRes == -1 ) &&
        ( pPictureData->idImageInfo.vRes == -1 ))
        pPictureData->idImageInfo.hRes =
        pPictureData->idImageInfo.vRes = MAKELFIXED( 72, 0 );
    else if( pPictureData->idImageInfo.hRes == -1 )
        pPictureData->idImageInfo.hRes = pPictureData->idImageInfo.vRes;
    else if( pPictureData->idImageInfo.vRes == -1 )
        pPictureData->idImageInfo.vRes = pPictureData->idImageInfo.hRes;

    // Initialize this to something impossible so that
    // size checking tests in ZoomPicture, etc will work
    // during startup
    pPictureData->zsZoomScroll.wCurZoomIndex = IMAGE_SIZE_FIRST - 1;

    // Create the picture window controls
    if( CreateViewerKids( hwndPicture, pPictureData ))
        goto Failed; // Errors reported by function

    pPictureData->spmsHScroll.wCurPos = 0;
    pPictureData->spmsVScroll.wCurPos = 0;

    // Set copy option default values
    SetOptionsDefaults( hwndPicture, pPictureData );

    if( pPictureData->qtoleOptions.bZoomHalf )
        wZoomIndex = IMAGE_SIZE_50;
    else if( pPictureData->qtoleOptions.bZoomDouble )
        wZoomIndex = IMAGE_SIZE_200;
    else
        wZoomIndex = IMAGE_SIZE_100;

    // Build picture. This initializes pPictureData->rcCurPictureRect.
    if( ZoomPicture( hwndPicture, wZoomIndex ))
        goto Failed; // Error displayed by function

    // At this point, the picture is in a window whose size
    // and position is the current MDI default . Now, resize to actual
    // size of picture, without clipping if possible

    // Get default corner
    GetWindowRect( hwndPicture, &rcWindow );
    MapWindowPoints( HWND_DESKTOP, ViewerQueryClientWindow(),
        (LPPOINT) &rcWindow, 2 );
    ptCorner = *((LPPOINT) &rcWindow.left);

    // Adjust window to fit normal size of picture
    rcWindow = pPictureData->rcCurPictureRect;
    // get client rect from picture rect in client coordinates
    ClientRectFromPicture( &rcWindow );
    // Now adjust for windows stuff
    AdjustWindowRect( &rcWindow,
        GetWindowLong( hwndPicture, GWL_STYLE ), FALSE );
    wWidth  = rcWindow.right  - rcWindow.left;
    wHeight = rcWindow.bottom - rcWindow.top;
    OffsetRect( &rcWindow, ptCorner.x - rcWindow.left,
        ptCorner.y - rcWindow.top );
    // rcWindow now contains the resized window positioned so that
    // the UL corner is at the MDI default position

    // Now see if it will fit on screen.
    GetClientRect( ViewerQueryClientWindow(), &rcclientClient );
    if( rcWindow.right > rcclientClient.right ) { // extends beyond right border
                                                  // Try to shift it to the left
        nDiff = rcclientClient.right - wWidth;
        rcWindow.left  = max( 0, nDiff );
        rcWindow.right = rcclientClient.right;
    }

    if( rcWindow.bottom > rcclientClient.bottom ) { // extends beyond bottom
                                                    // Try to shift it up
        nDiff = rcclientClient.bottom - wHeight;
        rcWindow.top    = max( 0, nDiff );
        rcWindow.bottom = rcclientClient.bottom;
    }

    MoveWindow( hwndPicture, rcWindow.left, rcWindow.top,
        rcWindow.right - rcWindow.left,
        rcWindow.bottom - rcWindow.top, TRUE );

    SetCursor( hcursorSave );

    SetFocus( hwndPicture );

    // Tell qtole.dll that the picture wnd has been opened
    if( ( lpOleData = ViewerQueryOleData()) && lpOleData->lpqtoleServer ) {
        qtoleOpenWnd.lStructSize  = sizeof( qtoleOpenWnd );
        qtoleOpenWnd.lVersion     = VERSION_1;
        qtoleOpenWnd.wObjectType  = PICTURE_OBJECT;
        qtoleOpenWnd.hwndObject   = hwndPicture;
        qtoleOpenWnd.lpObjectPath = pPictureData->szPicturePath;
        qtoleOpenWnd.lpObjectName = pPictureData->szPictureName;

        QTOLE_OpeningNewObjectWnd( lpOleData, &qtoleOpenWnd );
    }

    return  0L;

 Failed:
    if( hcursorSave )
        SetCursor( hcursorSave );

    if( pPictureData->phPicture != NULL)
        DisposePicture( pPictureData->phPicture );

    if( pPictureData->hpalPicture )
        DeleteObject( pPictureData->hpalPicture );

    SetWindowWord( hwndPicture, 0, 0 );

    if( pPictureData )
        LocalUnlock( hPictureData );
    if( hPictureData )
        LocalFree( hPictureData );

    return -1;

}


// Function: CheckDupEnumProc - Checks for duplicate pictures and
//                              fixes up titles if there are any
// --------------------------------------------------------------------
// Parameters: As required by Microsoft Windows
//
// Returns:    Always TRUE;
// --------------------------------------------------------------------
BOOL __export CALLBACK CheckDupEnumProc( HWND hwnd, LPARAM lParam )

// Look for duplicate pictures. Test is on path rather than just name

{
    char             szBuffer[50];       // Temp buffer
    HWND             hwndActivePicture;  // Handle of active picture wnd
    NPPICTUREDATA    pPictureData;       // -> enum wnd picture data struct
    NPPICTUREDATA    pActivePictureData; // -> active wnd picture data struct


    // skip active picture
    if( ( hwndActivePicture = (HWND) LOWORD( lParam )) == hwnd )
        return TRUE;

    if( !GetClassName( hwnd, szBuffer, sizeof( szBuffer )) ||
        lstrcmpi( szBuffer, VIEWER_PICTURE_CLASS ))
        return TRUE;

    pPictureData = (NPPICTUREDATA) GetWindowWord( hwnd, 0 );
    pActivePictureData =
        (NPPICTUREDATA) GetWindowWord( hwndActivePicture, 0 );
    if( !pPictureData || !pActivePictureData )
        return TRUE;

    if( !lstrcmpi( pPictureData->szPicturePath,
        pActivePictureData->szPicturePath )) { // Found a duplicate
        g.wPictureCount++;
        if( g.bCreating ) {
            if( pPictureData->wDuplicationIndex == 0 ) {
                pPictureData->wDuplicationIndex = 1;
                wsprintf( szBuffer, "%s:%u",
                    (LPSTR) pPictureData->szPictureName,
                    pPictureData->wDuplicationIndex );
                SetWindowText( hwnd, szBuffer );
            }
        }
        else {
            if( pPictureData->wDuplicationIndex >
                pActivePictureData->wDuplicationIndex ) {
                pPictureData->wDuplicationIndex--;
                wsprintf( szBuffer, "%s:%u",
                    (LPSTR) pPictureData->szPictureName,
                    pPictureData->wDuplicationIndex );
                SetWindowText( hwnd, szBuffer );
            }
            if( pPictureData->wDuplicationIndex == 1 )
                g.hwndFirstDup = hwnd;
        }

        if( pPictureData->hwndGetInfo &&
            !PostMessage( pPictureData->hwndGetInfo,
            WM_INFO_UPDATEFILENAME, 0, 0L ))
            SendMessage( pPictureData->hwndGetInfo,
            WM_INFO_UPDATEFILENAME, 0, 0L );
    }

    return TRUE;
}


// Function: ViewerEditCommands - Process WM_COMMAND, Edit popup messages
// --------------------------------------------------------------------
// Parameters: HWND   hwndPicture;    Picture window
//             WORD   wIDItem;        Menu id
//             WORD   wNotifyCode;    notification message
//
// Returns:    LONG   generally 0L
// --------------------------------------------------------------------
static LONG NEAR ViewerEditCommands
                   (HWND hwndPicture, WPARAM wIDItem, WORD wNotifyCode )

{
    NPPICTUREDATA  pPictureData; // -> picture data struct
    HDC            hdc;          // Handle to device context
    HDC            hdcMem;       // Handle to mem device context
    RECT           rcPicture;    // Picture rect
    HBITMAP        hBitmap;      // Handle to bitmap passed to clipboard
    HBITMAP        hSaveBitmap1; // Temp bitmap handle
    HCURSOR        hcursorSave;  // Saved cursor
    HBRUSH         hbrushSave;   // Prev brush
    DIBHandle      hbmpDIB;      // Handle of DIB bitmap passed to clipboard
    OSErr          oserr;        // Error return from DrawPictureFile
    HPALETTE       hpal;         // Palette handle
    LPQTOLE_OLEDATA  lpOleData;  // -> ole data
    HPALETTE       hpalSave;     // Prev palette


    if( !( pPictureData = (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOPICDATA, VIEWER_STRING_CAPTION, MB_OK );
        return 0L;
    }

    switch( wIDItem ) {
        case VIEWER_EDIT_COPYPICTURE:
            if( IsIconic( hwndPicture ))
                return 0L;

            // If ole, let ole do the copy
            if( ( lpOleData = ViewerQueryOleData()) && lpOleData->lpqtoleServer ) {
                PopulateOptionsStruct( hwndPicture, pPictureData );

                QTOLE_Copy( lpOleData, (LPQTOLE_OPTIONS) &pPictureData->qtoleOptions );

                // We must compensate for an error in certain drivers by
                // resetting the palette back the way we found it
                if( ViewerIsPalettized() &&
                    pPictureData->hpalPicture && ( hdc = GetDC( hwndPicture ))) {
                    hpalSave = SelectPalette( hdc, pPictureData->hpalPicture, 0 );
                    RealizePalette( hdc );

                    SelectPalette( hdc, hpalSave, 0 );
                    ReleaseDC( hwndPicture, hdc );
                }
            }
            else // No ole so do copy here
            {
                if( !( hdc = GetDC( hwndPicture ))) {
                    CommonTellUser( ViewerQueryResources(),
                        VIEWER_STRING_NODC,
                        VIEWER_STRING_CAPTION, MB_OK );
                    return 0L;
                }

                rcPicture.left = rcPicture.top = 0;
                rcPicture.right  = pPictureData->idImageInfo.width;
                rcPicture.bottom = pPictureData->idImageInfo.height;

                if( !( hBitmap = CreateCompatibleBitmap
                    ( hdc, rcPicture.right, rcPicture.bottom ))) {
                    CommonTellUser( ViewerQueryResources(),
                        VIEWER_STRING_COPYFAILED,
                        VIEWER_STRING_CAPTION, MB_OK );
                    ReleaseDC( hwndPicture, hdc );
                    return 0L;
                }

                oserr = 1L; // initialize to nonzero value
                if( (hdcMem = CreateCompatibleDC( hdc )) &&
                    ( hSaveBitmap1 = SelectObject( hdcMem, hBitmap ))) {
                    hcursorSave = SetCursor( LoadCursor( NULL, IDC_WAIT ));
                    hbrushSave = SelectObject( hdcMem, PICBACKGRNDBRUSH );
                    oserr = DrawPicture( hdcMem, pPictureData->phPicture,
                        &rcPicture, NULL );
                    SetCursor( hcursorSave );

                    // Unselect the bitmap and brush
                    SelectObject( hdcMem, hSaveBitmap1 );
                    if( hbrushSave )
                        SelectObject( hdcMem, hbrushSave );

                    if( !oserr ) {
                        OpenClipboard( hwndPicture );
                        EmptyClipboard();
                        // Give bitmap to clipboard if OK
                        if( !SetClipboardData( CF_BITMAP, hBitmap ))
                            DeleteObject( hBitmap );

                        // Now add palette if there is one
                        if( ( hpal = GetPicturePalette( pPictureData->phPicture )) &&
                            !SetClipboardData( CF_PALETTE, hpal ))
                            DeleteObject( hpal );

                        // Now try to add DIBitmap to clipboard
                        if( ( hbmpDIB =
                            PictureToDIB( pPictureData->phPicture )) &&
                            !SetClipboardData( CF_DIB, hbmpDIB ))
                            GlobalFree( hbmpDIB );

                        CloseClipboard();
                    }
                }
                else {
                    if( hBitmap )
                        DeleteObject( hBitmap );
                }

                if( oserr )
                    CommonTellUser( ViewerQueryResources(),
                    VIEWER_STRING_DRAWPICFAIL,
                    VIEWER_STRING_CAPTION, MB_OK );
                if( hdcMem )
                    DeleteDC( hdcMem );

                ReleaseDC( hwndPicture, hdc );
            }

            return 0L;

        case VIEWER_EDIT_OPTIONS:
            PopulateOptionsStruct( hwndPicture, pPictureData );

            if( ViewerGetOptions( hwndPicture, &pPictureData->qtoleOptions ))
                UpdatePictForOptions( hwndPicture, pPictureData, FALSE );

            return 0L;

        case VIEWER_EDIT_CANCELSEL:
            if( !IsRectEmpty( &pPictureData->qtoleOptions.rcSelection ) &&
                ( hdc = GetDC( hwndPicture ))) {
                DrawSelectionRect( hwndPicture, hdc, pPictureData );
                ReleaseDC( hwndPicture, hdc );
                SetRectEmpty( &pPictureData->qtoleOptions.rcSelection );
            }

            return 0L;
    }

    return 0L; // should never get here

}


// Function: InitSelection - Initiates the selection
// --------------------------------------------------------------------
// Parameters: HWND          hwndPicture      Handle of picture wnd
//             POINT         ptCursor         Cursor position
//
// Returns:    BOOL          TRUE if OK
// --------------------------------------------------------------------
static BOOL NEAR InitSelection( HWND hwndPicture, POINT ptCursor )

{
    NPPICTUREDATA     pPictureData;
    HDC               hdc;

    // First remove old selection
    if( pPictureData = (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )) {
        g.rcSelection = pPictureData->qtoleOptions.rcSelection;
        if( !IsRectEmpty( &g.rcSelection ) &&
            ( hdc = GetDC( hwndPicture ))) {
            DrawSelectionRect( hwndPicture, hdc, pPictureData );
            ReleaseDC( hwndPicture, hdc );
        }
    }

    if( hdc = GetDC( NULL )) {
        ClientToScreen( hwndPicture, &ptCursor );

        *(LPPOINT) &g.rcSelection.left  = ptCursor;
        *(LPPOINT) &g.rcSelection.right = ptCursor;

        DrawFocusRect( hdc, &g.rcSelection );

        ReleaseDC( NULL, hdc );

        return TRUE;
    }

    return FALSE;
}

// Function: MoveTheSelectRect - Move the selection rect
// --------------------------------------------------------------------
// Parameters: HWND          hwndPicture      Handle of picture wnd
//             POINT         ptCursor         Cursor position
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR MoveTheSelectRect( HWND hwndPicture, POINT ptCursor )

{
    HDC       hdc;

    if( hdc = GetDC( NULL )) {
        ClientToScreen( hwndPicture, &ptCursor );
        DrawFocusRect( hdc, &g.rcSelection );

        *(LPPOINT) &g.rcSelection.right = ptCursor;
        if( g.rcSelection.right < g.rcSelection.left )
            g.rcSelection.right = g.rcSelection.left;

        if( g.rcSelection.bottom < g.rcSelection.top )
            g.rcSelection.bottom = g.rcSelection.top;

        DrawFocusRect( hdc, &g.rcSelection );

        ReleaseDC( NULL, hdc );
    }

    return;
}

// Function: EndTheSelection - End the selection
// --------------------------------------------------------------------
// Parameters: HWND          hwndPicture      Handle of picture wnd
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR EndTheSelection( HWND hwndPicture )

{
    NPPICTUREDATA     pPictureData;
    int               nZoomMult;
    HDC               hdc;

    // Remove selection rect here and redraw later using function
    // This is necessary because roundoff in convertions can cause
    // one pixel differences in rect coordinates
    if( hdc = GetDC( NULL )) {
        DrawFocusRect( hdc, &g.rcSelection );
        ReleaseDC( NULL, hdc );
    }

    if( pPictureData = (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 )) {
        if( !IsRectEmpty( &g.rcSelection )) {
            MapWindowPoints( HWND_DESKTOP, hwndPicture,
                (LPPOINT) &g.rcSelection, 2 );

            ClientToPicture( (LPPOINT) &g.rcSelection, 2 );
            // rcCurPictureRect knows about scroll offsets.
            // These offsets are negative
            OffsetRect( &g.rcSelection,
                -pPictureData->rcCurPictureRect.left,
                -pPictureData->rcCurPictureRect.top );

            // Now convert to 100% zoom.  rcSelection will be stored
            // at 100% zoom. Copy will reflect actual zoom
            nZoomMult = (int) ViewerQueryZoomMultiplier
                ( pPictureData->zsZoomScroll.wCurZoomIndex );
            g.rcSelection.left   = MulDiv( g.rcSelection.left,   100, nZoomMult );
            g.rcSelection.top    = MulDiv( g.rcSelection.top,    100, nZoomMult );
            g.rcSelection.right  = MulDiv( g.rcSelection.right,  100, nZoomMult );
            g.rcSelection.bottom = MulDiv( g.rcSelection.bottom, 100, nZoomMult );

            // Now check that section is inside picture
            if( g.rcSelection.left < 0 )
                g.rcSelection.left = 0;
            if( g.rcSelection.top < 0 )
                g.rcSelection.top = 0;
            if( g.rcSelection.right > (int) pPictureData->idImageInfo.width )
                g.rcSelection.right = pPictureData->idImageInfo.width;
            if( g.rcSelection.bottom > (int) pPictureData->idImageInfo.height )
                g.rcSelection.bottom = pPictureData->idImageInfo.height;
            // g.rcSelection now has selection relative to UL corner
            // of picture at 100% zoom
        }

        pPictureData->qtoleOptions.rcSelection = g.rcSelection;

        // Now redraw the rect to leave it visible
        if( hdc = GetDC( hwndPicture )) {
            DrawSelectionRect( hwndPicture, hdc, pPictureData );
            ReleaseDC( hwndPicture, hdc );
        }
    }


    return;
}


// Function: ViewerImageCommands - Process WM_COMMAND, Image popup messages
// --------------------------------------------------------------------
// Parameters: HWND   hwndPicture;    Picture window
//             WORD   wIDItem;        Menu id
//             WORD   wNotifyCode;    notification message
//
// Returns:    LONG   generally 0L
// --------------------------------------------------------------------
static LONG NEAR ViewerImageCommands
                   (HWND hwndPicture, WPARAM wIDItem, WORD wNotifyCode )

{
    DLGPROC        lpDlgProc;    // -> dlg callback proc
    HWND           hInfoDialog;  // Handle to modeless info dialog
    NPPICTUREDATA  pPictureData; // -> picture data struct

    if( !(pPictureData =
        (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOPICDATA, VIEWER_STRING_CAPTION, MB_OK );
        return 0L;
    }

    switch( wIDItem ) {
        case VIEWER_IMAGE_GETINFO:
            if( pPictureData->hwndGetInfo ) {
                SetFocus( pPictureData->hwndGetInfo );
            }
            else if( !(lpDlgProc = (DLGPROC) MakeProcInstance
                ( (FARPROC) GetInfoDlgProc, ViewerQueryInstance())) ||
                !( hInfoDialog = CreateDialog( ViewerQueryResources(),
                MAKEINTRESOURCE( VIEWER_DLG_GETINFO ),
                ViewerQueryFrameWindow(), lpDlgProc ))) {
                CommonTellUser( ViewerQueryResources(),
                    VIEWER_STRING_NOMEMORY, NULL, MB_OK );
            }

            return 0L;

        case VIEWER_IMAGE_HALFSIZE:
        case VIEWER_IMAGE_NORMALSIZE:
        case VIEWER_IMAGE_DOUBLESIZE:
            // errors reported by function
            ZoomPicture( hwndPicture, wIDItem );
            return 0L;
    }

    return 0L; // should never get here

}


// Function: PaintThePicture - processes the WM_PAINT message when normal
// --------------------------------------------------------------------
// Parameters: HWND         hwndPicture     Handle of Picture window
//
// Returns:    LONG         0L if successful
// --------------------------------------------------------------------
static LONG NEAR PaintThePicture( HWND hwndPicture )

{
    PAINTSTRUCT     ps;              // Paint struct
    NPPICTUREDATA   pPictureData;    // -> picture data struct
    HCURSOR         hcursorSave;     // Save cursor handle
    HBRUSH          hbrushSave;      // Prev brush
    RECT            rcclientPicture; // Picture rect in client coordinates
    int             nRegion;         // Return from IntersectClipRegion
    OSErr           oserr;           // DrawPicture error return
    WORD            wIDError;        // Error message ID


    if( !( pPictureData =
        (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
        if( BeginPaint( hwndPicture, &ps ))
            EndPaint( hwndPicture, &ps );
        return 0L;
    }

    // Always paint the grow box. This must come before BeginPaint
    InvalidateRect( hwndPicture, &pPictureData->rcGrowBox, FALSE );

    if( !BeginPaint( hwndPicture, &ps )) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOMEMORY,
            VIEWER_STRING_CAPTION, MB_OK );
        return VIEWER_STRING_NOMEMORY;
    }

    // First draw the grow box and exclude the rect so that picture isn't
    // draw over it below
    PaintTheGrowBox( hwndPicture, ps.hdc, pPictureData );
    ExcludeClipRect( ps.hdc, pPictureData->rcGrowBox.left,
        pPictureData->rcGrowBox.top,
        pPictureData->rcGrowBox.right,
        pPictureData->rcGrowBox.bottom );

    // Note: rcCurPictureRect knows about scrolling offset. left and
    // top are negative if pict is scrolled. (0,0) is always the UL
    // corner of the displayed portion of the picture in picture
    // coordinates.

    rcclientPicture = pPictureData->rcCurPictureRect;

    // Convert to client coordinates since ps.hdc understands client
    // coordinates
    PictureToClient( (LPPOINT) &rcclientPicture, 2 );

    // Paint the background
    hbrushSave = SelectObject( ps.hdc, PICBACKGRNDBRUSH );
    SaveDC( ps.hdc );
    ExcludeClipRect( ps.hdc, rcclientPicture.left, rcclientPicture.top,
        rcclientPicture.right, rcclientPicture.bottom );
    FillRect( ps.hdc, &ps.rcPaint, PICBACKGRNDBRUSH );
    RestoreDC( ps.hdc, -1 );
    if( hbrushSave )
        SelectObject( ps.hdc, hbrushSave );

    // Check if picture needs to be painted
    SaveDC( ps.hdc );
    nRegion = IntersectClipRect( ps.hdc,
        rcclientPicture.left, rcclientPicture.top,
        rcclientPicture.right, rcclientPicture.bottom );
    if( ( nRegion == ERROR ) || ( nRegion == NULLREGION )) {
        RestoreDC( ps.hdc, -1 );
        EndPaint( hwndPicture, &ps );
        return 0L;
    }

    // Draw picture
    hcursorSave = SetCursor( LoadCursor( NULL, IDC_WAIT ));

    if( oserr = DrawPicture( ps.hdc, pPictureData->phPicture,
        &rcclientPicture, NULL )) {
        if( oserr == insufficientMemory )
            wIDError = VIEWER_STRING_NOMEMORY;
        else
            wIDError = VIEWER_STRING_DRAWPICFAIL;

        CommonTellUser( ViewerQueryResources(), wIDError,
            VIEWER_STRING_CAPTION, MB_OK );

        // Validate rect to prevent infinite loop
        ValidateRect( hwndPicture, NULL );
        SetCursor( hcursorSave );
        RestoreDC( ps.hdc, -1 );
        EndPaint( hwndPicture, &ps );

        return VIEWER_STRING_DRAWPICFAIL;
    }
    RestoreDC( ps.hdc, -1 );

    if( !IsRectEmpty( &pPictureData->qtoleOptions.rcSelection ))
        DrawSelectionRect( hwndPicture, ps.hdc, pPictureData );

    SetCursor( hcursorSave );

    // Done painting
    EndPaint( hwndPicture, &ps );

    return 0L;
}


// Function: DrawSelectionRect - Draws the selection rect
// --------------------------------------------------------------------
// Parameters: HWND            hwndPicture    HWND of picture wnd
//             HDC             hdc            HDC of picture wnd
//             NPPICTUREDATA   pPictureData
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR DrawSelectionRect
                ( HWND hwndPicture, HDC hdc, NPPICTUREDATA pPictureData )

{
    RECT       rcSelection;
    RECT       rcPicture;
    int        nZoomMult;
    int        nRegion;

    // Get picture rect and prevent drawing outside the picture
    GetClientRect( hwndPicture, &rcPicture );
    PictureRectFromClient( &rcPicture );

    nRegion = IntersectClipRect( hdc,
        rcPicture.left, rcPicture.top,
        rcPicture.right, rcPicture.bottom );
    if( ( nRegion == ERROR ) || ( nRegion == NULLREGION ))
        return;

    // Convert to current zoom
    rcSelection = pPictureData->qtoleOptions.rcSelection;
    nZoomMult = (int) ViewerQueryZoomMultiplier
        ( pPictureData->zsZoomScroll.wCurZoomIndex );
    rcSelection.left   = MulDiv( rcSelection.left,   nZoomMult, 100 );
    rcSelection.top    = MulDiv( rcSelection.top,    nZoomMult, 100 );
    rcSelection.right  = MulDiv( rcSelection.right,  nZoomMult, 100 );
    rcSelection.bottom = MulDiv( rcSelection.bottom, nZoomMult, 100 );

    // Offset for current scroll and convert to client coordinates
    OffsetRect( &rcSelection,
        pPictureData->rcCurPictureRect.left,
        pPictureData->rcCurPictureRect.top );
    PictureToClient( (LPPOINT) &rcSelection, 2 );

    DrawFocusRect( hdc, &rcSelection );

    return;
}

// Function: PaintTheIcon - processes the WM_PAINT message when iconic
// --------------------------------------------------------------------
// Parameters: HWND         hwndPicture     Handle of Picture window
//
// Returns:    LONG         0L
// --------------------------------------------------------------------
static LONG NEAR PaintTheIcon( HWND hwndPicture )

{
    PAINTSTRUCT        ps;           // Paint struct
    NPPICTUREDATA      pPictureData; // -> picture data struct
    HDC                hmemDC;       // Memory DC
    HBITMAP            hbitmapSave;  // Prev bitmap
    BITMAP             bm;           // Bitmap struct
    LPBITMAPINFOHEADER lpbmih;       // -> bitmapinfo header struct
    HPALETTE           hpalSave;     // Prev palette
    HBRUSH             hbrush;       // Background brush

    static RECT        rcPic = {
                        4, 4, 32, 32}; // Rect used in painting pic


    InvalidateRect( hwndPicture, NULL, FALSE );

    if( !BeginPaint( hwndPicture, &ps ))
        return 0L;

    if( !( pPictureData = (NPPICTUREDATA)
        GetWindowWord( hwndPicture, 0 )) ||
        ( !pPictureData->lpDIBIconMem && !pPictureData->hbitmapIcon )) {
        if( hbrush = CreateSolidBrush( GetSysColor( COLOR_APPWORKSPACE ))) {
            FillRect( ps.hdc, &ps.rcPaint, hbrush );
            DeleteObject( hbrush );
        }

        if( g.hpictIcon )
            DrawIcon( ps.hdc, 2, 2, g.hpictIcon );
        EndPaint( hwndPicture, &ps );
        return 0L;
    }

    // Will have DIB icon only if palettized
    if( pPictureData->lpDIBIconMem ) {
        lpbmih = (LPBITMAPINFOHEADER) pPictureData->lpDIBIconMem;

        // Select the current system palette into the dc so SetDIBits..
        // can do color matching. The system palette is set by the last
        // call to RealizePalette and so the current palette will
        // match the picture's palette only if the picture is active
        hpalSave = SelectPalette( ps.hdc, g.hpalCurrentSystem, FALSE );

        SetDIBitsToDevice( ps.hdc, rcPic.left, rcPic.top,
            (WORD) lpbmih->biWidth, (WORD) lpbmih->biHeight,
            0, 0, 0, (WORD) lpbmih->biHeight,
            (LPVOID) ((LPSTR) lpbmih +
            (WORD) lpbmih->biSize + SIZE_COLOR_TABLE ),
            pPictureData->lpDIBIconMem, DIB_RGB_COLORS );

        if( hpalSave )
            SelectPalette( ps.hdc, hpalSave, FALSE );

        // Now paint the background
        SaveDC( ps.hdc );
        ExcludeClipRect( ps.hdc, rcPic.left, rcPic.top,
            rcPic.right, rcPic.bottom );
        // COLOR_APPWORKSPACE is standard MDI background color
        if( hbrush = CreateSolidBrush( GetSysColor( COLOR_APPWORKSPACE ))) {
            FillRect( ps.hdc, &ps.rcPaint, hbrush );
            DeleteObject( hbrush );
        }
        RestoreDC( ps.hdc, -1 );

        // Now draw the picture frame
        if( g.hpictIcon )
            DrawIcon( ps.hdc, 2, 2, g.hpictIcon );
    }
    else {
        hmemDC = NULL;
        if( pPictureData->hbitmapIcon &&
            ( hmemDC = CreateCompatibleDC( ps.hdc )) &&
            ( hbitmapSave =
            SelectObject( hmemDC, pPictureData->hbitmapIcon ))) {
            GetObject( pPictureData->hbitmapIcon, sizeof( BITMAP ), &bm );

            BitBlt( ps.hdc, 0, 0, bm.bmWidth, bm.bmHeight,
                hmemDC, 0, 0, SRCCOPY );
            SelectObject( hmemDC, hbitmapSave );
        }
        else {
            if( hbrush = CreateSolidBrush( GetSysColor( COLOR_APPWORKSPACE ))) {
                FillRect( ps.hdc, &ps.rcPaint, hbrush );
                DeleteObject( hbrush );
            }

            if( g.hpictIcon )
                DrawIcon( ps.hdc, 2, 2, g.hpictIcon );
        }

        if( hmemDC )
            DeleteDC( hmemDC );
    }

    EndPaint( hwndPicture, &ps );

    return 0L;

}

// Function: BuildIconBitmap - builds the icon bitmap
// --------------------------------------------------------------------
// Parameters: HWND            hwndPicture     Handle of picture wnd
//             NPPICTUREDATA   pPictureData    -> picture data struct
//
// Returns:    LONG         0L
// --------------------------------------------------------------------
static BOOL NEAR BuildIconBitmap
                           ( HWND hwndPicture, NPPICTUREDATA pPictureData )

{
    HDC              hdc;            // DC of picture
    HDC              hmemDC;         // Memory dc
    HBRUSH           hbrush;         // Temp background brush
    HBRUSH           hbrushSave;     // Prev brush
    HBITMAP          hbitmapSave;    // Prev bitmap
    HBITMAP          hbitmap;        // Handle of icon bitmap
    HGLOBAL          hbmi;           // Memory handle for DIB info struct
    HGLOBAL          hnewbmi;        // Temp memory handle
    BITMAPINFOHEADER bi;             // Bitmap info header struct
    LPVOID           lpbmi;          // -> bitmap info struct
    BITMAP           bm;             // BITMAP struct
    HPALETTE         hpalSave;       // Prev palette
    HCURSOR          hcursorSave;    // Prev cursor
    RECT             rcPicRect;      // Picture rect
    WORD             wIconBmpWidth;  // Icon width
    WORD             wIconBmpHeight; // Icon height
    WORD             wProportional;  // Temp

    static RECT      rcIcon = {
                         2, 2, 34, 34}; // Rect used in painting icon
    static RECT      rcPicbmp = {
                         4, 4, 32, 32}; // Rect used in painting pic
    static RECT      rcPicdib = {
                         0, 0, 28, 28}; // Rect used in painting pic
    static RECT      rcBitmap =         // Overall bitmap dimensions
                        {0, 0, 36, 36 };


    // Macro used to align on ULONG boundary
    // From 3.1 SDK sample code, dib.h
    #define WIDTHBYTES(i)  ( ( i + 31 ) / 32 * 4 )

    pPictureData->hbitmapIcon  = NULL;
    pPictureData->lpDIBIconMem = NULL;

    if( !( hdc = GetDC( hwndPicture ))) {
        return FALSE;
    }

    if( !( hmemDC = CreateCompatibleDC( hdc ))) {
        ReleaseDC( hwndPicture, hdc );
        return FALSE;
    }

    hbitmap  = NULL;
    hpalSave = NULL;
    hcursorSave = SetCursor( LoadCursor( NULL, IDC_WAIT ));

    // Get picture rect with proportions of the original pic
    rcPicRect = rcPicbmp;
    wIconBmpWidth  = rcPicbmp.right - rcPicbmp.left;
    wIconBmpHeight = rcPicbmp.bottom - rcPicbmp.top;
    if( pPictureData->idImageInfo.width >
        pPictureData->idImageInfo.height ) {
        wProportional = MulDiv( wIconBmpHeight,
            pPictureData->idImageInfo.width,
            pPictureData->idImageInfo.height );
        rcPicRect.left  -= ( wProportional - wIconBmpWidth ) / 2;
        rcPicRect.right = rcPicRect.left + wProportional;
    }
    else if( pPictureData->idImageInfo.width <
        pPictureData->idImageInfo.height ) {
        wProportional = MulDiv( wIconBmpWidth,
            pPictureData->idImageInfo.height,
            pPictureData->idImageInfo.width );
        rcPicRect.top   -= ( wProportional - wIconBmpHeight ) / 2;
        rcPicRect.bottom = rcPicRect.top + wProportional;
    }

    if( ( hbitmap = CreateCompatibleBitmap( hdc,
        rcBitmap.right, rcBitmap.bottom )) &&
        ( hbitmapSave = SelectObject( hmemDC, hbitmap ))) { //First fill the background
        if( hbrush = CreateSolidBrush( GetSysColor( COLOR_APPWORKSPACE ))) {
            FillRect( hmemDC, &rcBitmap, hbrush );
            DeleteObject( hbrush );
        }

        // Now draw the picture.
        // Keep picture inside the allowed rect
        SaveDC( hmemDC );
        IntersectClipRect( hmemDC, rcPicbmp.left, rcPicbmp.top,
            rcPicbmp.right, rcPicbmp.bottom );

        hbrushSave = SelectObject( hmemDC, PICBACKGRNDBRUSH );
        if( DrawPicture( hmemDC, pPictureData->phPicture, &rcPicRect, NULL )) {
            CommonTellUser( ViewerQueryResources(),
                VIEWER_STRING_DRAWPICFAIL,
                VIEWER_STRING_CAPTION, MB_OK );
        }
        SelectObject( hmemDC, hbrushSave );
        RestoreDC( hmemDC, -1 );

        // Now draw the picture frame
        if( g.hpictIcon )
            DrawIcon( hmemDC, 2, 2, g.hpictIcon );

        SelectObject( hmemDC, hbitmapSave );

        pPictureData->hbitmapIcon = hbitmap;
        hbitmap = NULL;
    }
    else {
        goto IconFailed;
    }


    if( ViewerIsPalettized()) // Palettized, use DIB bitmap, but only for picture
    { // Should always be a palette but test anyway
        if( pPictureData->hpalPicture &&
            !( hpalSave = SelectPalette
            ( hdc, pPictureData->hpalPicture, FALSE ))) {
            CommonTellUser( ViewerQueryResources(),
                VIEWER_STRING_SELPALFAILED, NULL, MB_OK );
            goto IconFailed;
        }

        // Need to offset rcPicRect because of differences btwn
        // the origins of rcPicbmp and rcPicdib; ( 4, 4) vs ( 0, 0)
        OffsetRect( &rcPicRect, -rcPicbmp.left, -rcPicbmp.top );

        if( ( hbitmap = CreateCompatibleBitmap( hdc,
            rcPicdib.right, rcPicdib.bottom )) &&
            ( hbitmapSave = SelectObject( hmemDC, hbitmap ))) {
            hbrushSave = SelectObject( hmemDC, PICBACKGRNDBRUSH );
            if( DrawPicture( hmemDC, pPictureData->phPicture,
                &rcPicRect, NULL )) {
                CommonTellUser( ViewerQueryResources(),
                    VIEWER_STRING_DRAWPICFAIL,
                    VIEWER_STRING_CAPTION, MB_OK );
                SelectObject( hmemDC, hbitmapSave );
                SelectObject( hmemDC, hbrushSave );
                goto IconFailed;
            }
            SelectObject( hmemDC, hbrushSave );
            SelectObject( hmemDC, hbitmapSave );
        }
        else {
            goto IconFailed;
        }

        // Now convert to DIB bitmap
        // Initialize bitmap info header struct
        GetObject( hbitmap, sizeof( BITMAP ), &bm );

        bi.biSize          = sizeof( BITMAPINFOHEADER );
        bi.biWidth         = bm.bmWidth;
        bi.biHeight        = bm.bmHeight;
        bi.biPlanes        = 1;
        bi.biBitCount      = bm.bmPlanes * bm.bmBitsPixel;
        bi.biCompression   = BI_RGB;
        bi.biSizeImage     = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrUsed       = 0;
        bi.biClrImportant  = 0;

        if( !( hbmi = GlobalAlloc( GHND, bi.biSize + SIZE_COLOR_TABLE ))) {
            CommonTellUser( ViewerQueryResources(),
                VIEWER_STRING_NOMEMORY, NULL, MB_OK );
            goto IconFailed;
        }
        lpbmi = (LPVOID) GlobalLock( hbmi );

        // First get bitmap size by using NULL argument
        *(LPBITMAPINFOHEADER) lpbmi = bi;
        GetDIBits( hdc, hbitmap, 0, bm.bmHeight, NULL,
            (LPBITMAPINFO) lpbmi, DIB_RGB_COLORS );
        bi = *(LPBITMAPINFOHEADER) lpbmi;
        // If driver failed to set size, make a guess
        if( bi.biSizeImage == 0 )
            bi.biSizeImage = bi.biHeight *
            WIDTHBYTES( (DWORD) bm.bmWidth * bi.biBitCount );

        // Now realloc to get space for bitmap bits
        GlobalUnlock( hbmi );
        if( !( hnewbmi = GlobalReAlloc( hbmi,
            bi.biSize + SIZE_COLOR_TABLE + bi.biSizeImage, GHND ))) {
            CommonTellUser( ViewerQueryResources(),
                VIEWER_STRING_NOMEMORY, NULL, MB_OK );
            GlobalFree( hbmi );
            goto IconFailed;
        }
        hbmi = hnewbmi;
        lpbmi = (LPVOID) GlobalLock( hbmi );

        // Now get bitmap bits
        if( bm.bmHeight != GetDIBits( hdc, hbitmap, 0, bm.bmHeight,
            (LPVOID)((LPSTR) lpbmi + (WORD) bi.biSize + SIZE_COLOR_TABLE ),
            (LPBITMAPINFO) lpbmi, DIB_RGB_COLORS )) {
            CommonTellUser( ViewerQueryResources(),
                VIEWER_STRING_NOMAKEDIB, NULL, MB_OK );
            GlobalUnlock( hbmi );
            GlobalFree( hbmi );
            goto IconFailed;
        }

        // Done. Save -> to DIB and toss device dependent bitmap
        pPictureData->lpDIBIconMem = (LPBITMAPINFO) lpbmi;
        DeleteObject( pPictureData->hbitmapIcon );
        pPictureData->hbitmapIcon  = NULL;

        DeleteObject( hbitmap );
        SelectPalette( hdc, hpalSave, FALSE );
    }

    SetCursor( hcursorSave );
    ReleaseDC( hwndPicture, hdc );
    DeleteDC ( hmemDC );

    return TRUE;

 IconFailed:
    if( hcursorSave )
        SetCursor( hcursorSave );
    if( hpalSave )
        SelectPalette( hdc, hpalSave, FALSE );
    if( hbitmap )
        DeleteObject( hbitmap );

    DeleteDC( hmemDC );
    ReleaseDC( hwndPicture, hdc );

    return FALSE;

}


// Function: StoreCurrentSystemPalette - stores the current system palette just
//                                       before calling RealizePalette.
//                                       This is also called during frame
//                                       wnd WM_PALETTECHANGED processing
// --------------------------------------------------------------------
// Parameters: HPALETTE      hpalPicture  Palette of picture that realized
//                           palette, otherwise NULL
//
// Returns:    VOID
// --------------------------------------------------------------------
VOID FAR StoreCurrentSystemPalette( HPALETTE hpalPicture )

{
    if( hpalPicture )
        g.hpalCurrentSystem = hpalPicture;

    return;
}


// Function: KeyDownMessage - processes the WM_KEYDOWN message
// --------------------------------------------------------------------
// Parameters: HWND         hwndPicture    Handle of picture window
//             WPARAM       wParam         WM_KEYDOWN wParam
//             LPARAM       lParam         WM_KEYDOWN lParam
//
// Returns:    LONG         Generally 0L
// --------------------------------------------------------------------
static LONG NEAR KeyDownMessage
                   ( HWND hwndPicture, WPARAM wParam, LPARAM lParam )

// Process keyboard interface messages

{
    NPPICTUREDATA   pPictureData; // -> picture data struct
    WORD            wScroll;      // Scrolling parameter

    switch( wParam ) {
        case VK_LEFT:
        case VK_RIGHT:
            if( ISKEYDOWN( VK_CONTROL ))
                wScroll = ( wParam == VK_LEFT ) ? SB_PAGELEFT: SB_PAGERIGHT;
            else
                wScroll = ( wParam == VK_LEFT ) ? SB_LINELEFT: SB_LINERIGHT;

            return ProcessHorzScroll( hwndPicture, wScroll, 0 );

        case VK_UP:
        case VK_DOWN:
            if( ISKEYDOWN( VK_CONTROL ))
                wScroll = ( wParam == VK_UP ) ? SB_PAGEUP: SB_PAGEDOWN;
            else
                wScroll = ( wParam == VK_UP ) ? SB_LINEUP: SB_LINEDOWN;

            return ProcessVertScroll( hwndPicture, wScroll, 0 );

        case VK_PRIOR:
        case VK_NEXT:
            if( ISKEYDOWN( VK_CONTROL ))
                return ProcessHorzScroll( hwndPicture,
                ( wParam == VK_PRIOR ? SB_PAGELEFT: SB_PAGERIGHT ), 0 );
            else
                return ProcessVertScroll( hwndPicture,
                ( wParam == VK_PRIOR ? SB_PAGEUP: SB_PAGEDOWN ), 0 );

        case VK_HOME:
        case VK_END:
            return ScrollToCorner( hwndPicture, wParam );

        case VK_ADD:
        case VK_SUBTRACT:
            if( (pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 )) &&
                pPictureData->zsZoomScroll.hwnd )
                return SendMessage( pPictureData->zsZoomScroll.hwnd,
                WM_KEYDOWN, wParam, lParam );
            break;

    }

    return 0L;

}


// Function: PrintPicture - prints indicated number of copies of
//                          the picture
// --------------------------------------------------------------------
// Parameters: HWND         hwndPicture     Handle of Picture window
//             LPPRINTDLG   lppd            -> to PRINTDLG struct created
//                                          by print common dialog
//
// Returns:    LONG         0L if successful
// --------------------------------------------------------------------
static LONG NEAR PrintPicture( HWND hwndPicture, LPPRINTDLG lppd )

{
    NPPICTUREDATA   pPictureData; // -> picture data struct
    WORD            i;            // Counter
    DOCINFO         diDocInfo;    // Document info struct
    BOOL            bError;       // Error flag
    RECT            rcPicture;    // Picture rect
    int             nError;       // Error return
    int             xRes;         // Horz printer resolution
    int             yRes;         // Vert printer resolution
    int             xOffset;      // Horz offset to center picture
    int             yOffset;      // Vert offset to center picture
    OSErr           oserr;        // Error return from DrawPictureFile
    WORD            wWidthPage;   // Width of printed page in pixels
    WORD            wHeightPage;  // Height of printed page in pixels


    if( !(pPictureData =
        (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOPICDATA, NULL, MB_OK );
        // Turn off not reported flag
        return SP_ERROR &  ~SP_NOTREPORTED;
    }

    diDocInfo.cbSize      = sizeof( DOCINFO );
    diDocInfo.lpszDocName = pPictureData->szPictureName;
    diDocInfo.lpszOutput  = (LPSTR) NULL;

    bError = FALSE;
    oserr  = 0L;
    nError = SP_ERROR;

    wWidthPage  = GetDeviceCaps( lppd->hDC, HORZRES );
    wHeightPage = GetDeviceCaps( lppd->hDC, VERTRES );

    // get resolution of printer
    xRes = MulDiv( wWidthPage, 254,
        GetDeviceCaps( lppd->hDC, HORZSIZE ) * 10 );
    yRes = MulDiv( wHeightPage, 254,
        GetDeviceCaps( lppd->hDC, VERTSIZE ) * 10 );

    // Scale picture rect for differences in resolution
    rcPicture.right  = MulDiv( pPictureData->idImageInfo.width,
        xRes, HIWORD( pPictureData->idImageInfo.hRes ));
    rcPicture.bottom = MulDiv( pPictureData->idImageInfo.height,
        yRes, HIWORD( pPictureData->idImageInfo.vRes ));
    rcPicture.left = rcPicture.top = 0;

    // Now make sure that picture fits on page
    if( rcPicture.right > (int) wWidthPage ) {
        rcPicture.bottom = MulDiv( rcPicture.bottom,
            wWidthPage, rcPicture.right );
        rcPicture.right = wWidthPage;
    }
    if( rcPicture.bottom > (int) wHeightPage ) {
        rcPicture.right = MulDiv( rcPicture.right,
            wHeightPage, rcPicture.bottom );
        rcPicture.bottom = wHeightPage;
    }

    // Now center rect on page
    xOffset = ( wWidthPage  - rcPicture.right ) / 2;
    yOffset = ( wHeightPage - rcPicture.bottom ) / 2;
    OffsetRect( &rcPicture, xOffset, yOffset );

    // Start printing. Loop over number of copies
    if( StartDoc( lppd->hDC, &diDocInfo ) > 0 ) {
        for( i=0; (i < lppd->nCopies) && !bError; i++) {
            if( StartPage( lppd->hDC ) > 0 ) {
                if( (oserr = DrawPicture( lppd->hDC,
                    pPictureData->phPicture, &rcPicture, NULL )) != 0L ) { // DrawPicture failed, abort the print
                    AbortDoc( lppd->hDC );
                    bError = TRUE;
                }
                else {
                    if( ( nError = EndPage( lppd->hDC )) < 0 )
                        bError = TRUE;
                }
            }
            else
                bError = TRUE;
        }
    }
    else
        bError = TRUE;

    if( !bError )
        EndDoc( lppd->hDC );
    else if( oserr ) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_DRAWPICFAIL,
            VIEWER_STRING_PRT_CAPTION, MB_OK );
        // Error has been reported, turn off "not reported" bit
        nError = SP_ERROR &  ~SP_NOTREPORTED;
    }

    return (LONG) nError;
}


// Function: GetInfoDlgProc - Get Info dialog proc
// --------------------------------------------------------------------
// Parameters: As required by Microsoft Windows
//
// Returns:    As required by Microsoft Windows
// --------------------------------------------------------------------
BOOL __export CALLBACK GetInfoDlgProc
    ( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam )

{
    NPPICTUREDATA  pPictureData; // -> picture data struct
    HWND           hwndPicture;  // Handle to picture window
    HWND           hwndCtl;      // Handle of control
    HDC            hdc;          // DC of control
    int            nHeight;      // Text height


    switch( msg ) {
        case WM_INITDIALOG:
            hwndPicture = (HWND) SendMessage
                ( ViewerQueryClientWindow(), WM_MDIGETACTIVE, 0, 0L );

            // Use property to associate picture window handle with
            // info dialog. This is necessary because MDI makes frame
            // window the parent of all dialogs no matter what handle
            // is used in the CreateDialog call
            SetProp( hdlg, GETINFOPICTUREPROP, (HANDLE) hwndPicture );


            if( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 )) {
                pPictureData->hwndGetInfo = hdlg;

                if( hdc = GetDC( hdlg )) {
                    nHeight = -MulDiv( 8, GetDeviceCaps( hdc, LOGPIXELSY ), 72 );
                    if( pPictureData->hfInfo = MakeAnArialFont( hdc, nHeight )) {
                        hwndCtl = GetWindow( hdlg, GW_CHILD );
                        while( hwndCtl ) {
                            if( GetDlgCtrlID( hwndCtl ) != IDOK )
                                SendMessage( hwndCtl, WM_SETFONT,
                                (WPARAM) pPictureData->hfInfo, 0 );
                            hwndCtl = GetWindow( hwndCtl, GW_HWNDNEXT );
                        }
                    }
                    ReleaseDC( hdlg, hdc );
                }

                FillPictureInfo( hdlg, pPictureData );
            }

            return TRUE;

        case WM_ACTIVATE:
            ViewerSetActiveModeless( wParam, hdlg );
            return TRUE;

        case WM_COMMAND:
            return DestroyWindow( hdlg );

        // WM_USER messages

        case WM_INFO_UPDATEFILENAME:
            hwndPicture = (HWND) GetProp( hdlg, GETINFOPICTUREPROP );
            if( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 ))
                UpdateInfoFileName( hdlg, pPictureData );

            break;

        case WM_INFO_CURRENTSIZE:
            hwndPicture = (HWND) GetProp( hdlg, GETINFOPICTUREPROP );
            if( pPictureData = (NPPICTUREDATA)
                GetWindowWord( hwndPicture, 0 ))
                UpdateInfoCurrentSize( hdlg, pPictureData );

            break;

        // end WM_USER messages

        case WM_DESTROY:
            hwndPicture = (HWND) GetProp( hdlg, GETINFOPICTUREPROP );
            if( IsWindow( hwndPicture ) && ( pPictureData =
                (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
                pPictureData->hwndGetInfo = NULL;

                if( pPictureData->hfInfo )
                    DeleteObject( pPictureData->hfInfo );
                pPictureData->hfInfo = NULL;
            }

            RemoveProp( hdlg, GETINFOPICTUREPROP );

            return FALSE;
    }

    return FALSE;

}


// Function: FillPictureInfo - Fills dialog controls with picture data
// --------------------- -----------------------------------------------
// Parameters: HWND           hdlg             Handle of dialog wnd
//             NPPICTUREDATA  pPictureData     -> to PICTUREDATA struct
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR FillPictureInfo( HWND hdlg, NPPICTUREDATA pPictureData )

{
    char      szBuffer[50];  // buffer
    char      szFormat[20];  // Format buffer
    WORD      wNormalWidth;  // Normal width of picture
    WORD      wNormalHeight; // Normal height of picture
    DWORD     dwColors;      // Number of colors
    DWORD     dwSize;        // Uncompressed size
    WORD      wIDString;     // Resource string id


    // Picture name: Append duplication index if > 0
    UpdateInfoFileName( hdlg, pPictureData );

    // Filesize
    SetDlgItemText( hdlg, IMAGE_INFO_FILESIZE,
        (LPSTR) pPictureData->szFileSizeOnDisk );

    // Current Width and Height
    UpdateInfoCurrentSize( hdlg, pPictureData );

    // Normal Width and Height
    wNormalWidth  = pPictureData->idImageInfo.width;
    wNormalHeight = pPictureData->idImageInfo.height;
    LoadString( ViewerQueryResources(), VIEWER_STRING_WANDH,
        szFormat, sizeof( szFormat ));
    wsprintf( szBuffer, szFormat, wNormalWidth, wNormalHeight );
    SetDlgItemText( hdlg, IMAGE_INFO_WANDH, szBuffer );


    // Resolution
    if( ((LONG) pPictureData->idImageInfo.hRes == -1L ) ||
        ((LONG) pPictureData->idImageInfo.hRes == 0L ))
        LoadString( ViewerQueryResources(),
        VIEWER_STRING_NORESOLUTION, szBuffer, sizeof( szBuffer ));
    else {
        LoadString( ViewerQueryResources(),
            VIEWER_STRING_RESOLUTION, szFormat, sizeof( szFormat ));
        wsprintf( szBuffer, szFormat,
            HIWORD( pPictureData->idImageInfo.hRes ));
    }
    SetDlgItemText( hdlg, IMAGE_INFO_RESOLUTION, szBuffer );

    // Normal colors
    switch( pPictureData->idImageInfo.depth ) {
        case 1:      // Black and White
            wIDString = VIEWER_STRING_CLRS_BANDW;      
            break;
        case 1 + 32: // 2 Grays
            wIDString = VIEWER_STRING_CLRS_2GRAYS;     
            break;
        case 2:      // 4 Colors
            wIDString = VIEWER_STRING_CLRS_4COLORS;    
            break;
        case 2 + 32: // 4 Grays
            wIDString = VIEWER_STRING_CLRS_4GRAYS;     
            break;
        case 4:      // 16 Colors
            wIDString = VIEWER_STRING_CLRS_16COLORS;   
            break;
        case 4 + 32: // 16 Grays
            wIDString = VIEWER_STRING_CLRS_16GRAYS;    
            break;
        case 8:      // 256 Colors
            wIDString = VIEWER_STRING_CLRS_256COLORS;  
            break;
        case 8 + 32: // 256 Grays
            wIDString = VIEWER_STRING_CLRS_256GRAYS;   
            break;
        case 16:     // Thousands of Colors
            wIDString = VIEWER_STRING_CLRS_THOUSANDS;  
            break;
        case 24:     // Millions of Colors
            wIDString = VIEWER_STRING_CLRS_MILLIONS;   
            break;
        case 32:     // Millions of Colors+
            wIDString = VIEWER_STRING_CLRS_MILLNSPLUS; 
            break;
        default:
            wIDString = 0xffff;
            if( pPictureData->idImageInfo.depth < 32 ) {
                LoadString( ViewerQueryResources(),
                    VIEWER_STRING_COLORS, szFormat, sizeof( szFormat ));
                dwColors = 1L << pPictureData->idImageInfo.depth;
                wsprintf( szBuffer, szFormat, dwColors );
            }
            else {
                szBuffer[0] = '\0';
            }
            break;
    }
    if( wIDString != 0xffff )
        LoadString( ViewerQueryResources(),
        wIDString, szBuffer, sizeof( szBuffer ));
    SetDlgItemText( hdlg, IMAGE_INFO_COLORS, szBuffer );


    // Compressor. Compressor type displayed is the string stored for use in
    //             the banner bar rather that the DWORD value such as 'jpeg'
    SetDlgItemText( hdlg, IMAGE_INFO_COMPRESSOR,
        pPictureData->szPictType );


    // Quality
    if( pPictureData->idImageInfo.CodecType ) {
        *((PDWORD) &szBuffer) = pPictureData->idImageInfo.CodecType;
        szBuffer[ sizeof(DWORD) ] = '\0';
    }
    if( !pPictureData->idImageInfo.CodecType ||
        !lstrcmpi( szBuffer, "raw " )) {
        szBuffer[0] = '\0';
    }
    else {
        LoadString( ViewerQueryResources(),
            VIEWER_STRING_QUALITY, szBuffer, sizeof( szBuffer ));
    }
    SetDlgItemText( hdlg, IMAGE_INFO_QUALITY, szBuffer );


    // Uncompressed size
    dwSize = ( (DWORD) wNormalWidth * wNormalHeight *
        pPictureData->idImageInfo.depth ) / 8L;
    if( dwSize < 1000L ) {
        wIDString = VIEWER_STRING_SIZEBYTES;
    }
    else {
        dwSize /= 1000L;
        wIDString = VIEWER_STRING_SIZEKBYTES;
    }

    LoadString( ViewerQueryResources(), wIDString,
        szFormat, sizeof( szFormat ));
    wsprintf( szBuffer, szFormat, dwSize );
    SetDlgItemText( hdlg, IMAGE_INFO_UNCOMPSIZE, szBuffer );

    return;
}


// Function: UpdateInfoFileName - Updates the file name with the current
//                                instance count in the info dialog
// --------------------- -----------------------------------------------
// Parameters: HWND           hdlg             Handle of dialog wnd
//             NPPICTUREDATA  pPictureData     -> to PICTUREDATA struct
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR UpdateInfoFileName
                        ( HWND hdlg, NPPICTUREDATA pPictureData )

{
    char      szBuffer[MAX_NAME_LEN + 10]; // Name buffer
    char      szFormat[20];                // Format buffer

    lstrcpy( szFormat, "%s%s" );
    if( pPictureData->wDuplicationIndex > 0 )
        lstrcat( szFormat, ":%u" );
    wsprintf( szBuffer, szFormat, (LPSTR) pPictureData->szPictureName,
        (LPSTR) pPictureData->szPictureExt,
        pPictureData->wDuplicationIndex );
    AnsiUpper( szBuffer );
    SetDlgItemText( hdlg, IMAGE_INFO_NAME, (LPSTR) szBuffer );

    return;
}

// Function: UpdateInfoCurrentSize - Updates the current size in the
//                                   info dialog
// --------------------- -----------------------------------------------
// Parameters: HWND           hdlg             Handle of dialog wnd
//             NPPICTUREDATA  pPictureData     -> to PICTUREDATA struct
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR UpdateInfoCurrentSize
                        ( HWND hdlg, NPPICTUREDATA pPictureData )

{
    char      szBuffer[50]; // buffer
    char      szFormat[20]; // Format buffer

    LoadString( ViewerQueryResources(), VIEWER_STRING_WANDH,
        szFormat, sizeof( szFormat ));
    wsprintf( szBuffer, szFormat,
        pPictureData->rcCurPictureRect.right -
        pPictureData->rcCurPictureRect.left,
        pPictureData->rcCurPictureRect.bottom -
        pPictureData->rcCurPictureRect.top );
    SetDlgItemText( hdlg, IMAGE_INFO_CURSIZE, szBuffer );

    return;
}


// Function: InitializePopupMenus - Called just before the popup menus
//                                  are displayed
// --------------------------------------------------------------------
// Parameters: HWND         hwndPicture     Handle of Picture window
//             HMENU        hmenuPopup      Handle of popup menu
//             int          nPopupIndex     Index of popup
//
// Returns:    LONG         0L if successful
// --------------------------------------------------------------------
static LONG NEAR InitializePopupMenus
                ( HWND hwndPicture, HMENU hmenuPopup, int nPopupIndex )

{
    NPPICTUREDATA    pPictureData;      // -> picture data struct
    char             szTitle[64];       // OLE Client doc title
    char             szItemFormat1[32]; // Item format string
    char             szItem1[128];      // New item string
    char             szItemFormat2[32]; // Item format string
    char             szItem2[128];      // New item string

    if( !(pPictureData =
        (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))) {
        CommonTellUser( ViewerQueryResources(),
            VIEWER_STRING_NOPICDATA, VIEWER_STRING_CAPTION, MB_OK );
        return 0L;
    }

    // Decrement index if maximized since MDI adds a system menu item
    // which puts count off by one
    if( IsZoomed( hwndPicture ))
        nPopupIndex--;

    if( nPopupIndex == MENU_FILE_POS ) { // See if wnd is an activated client object
        if( QTOLE_IsActiveObjectWnd
            ( ViewerQueryOleData(), hwndPicture, szTitle )) {
            LoadString( ViewerQueryResources(),
                VIEWER_STRING_OLECLOSE, szItemFormat1, sizeof( szItemFormat1 ));
            wsprintf( szItem1, szItemFormat1, (LPSTR) szTitle );

            LoadString( ViewerQueryResources(),
                VIEWER_STRING_OLEEXIT, szItemFormat2, sizeof( szItemFormat2 ));
            wsprintf( szItem2, szItemFormat2, (LPSTR) szTitle );
        }
        else {
            LoadString( ViewerQueryResources(),
                VIEWER_STRING_CLOSE, szItem1, sizeof( szItem1 ));
            LoadString( ViewerQueryResources(),
                VIEWER_STRING_EXIT, szItem2, sizeof( szItem2 ));
        }

        DeleteMenu( hmenuPopup, VIEWER_FILE_CLOSE, MF_BYCOMMAND );
        InsertMenu( hmenuPopup, 1, MF_BYPOSITION,
            VIEWER_FILE_CLOSE, szItem1 );
        DeleteMenu( hmenuPopup, VIEWER_FILE_EXIT, MF_BYCOMMAND );
        InsertMenu( hmenuPopup, (UINT) -1, MF_BYPOSITION,
            VIEWER_FILE_EXIT, szItem2 );
    }
    else if( nPopupIndex == MENU_EDIT_POS ) {
        EnableMenuItem( hmenuPopup, VIEWER_EDIT_COPYPICTURE,
            ( IsIconic( hwndPicture ) ?  MF_GRAYED: MF_ENABLED ) |
            MF_BYCOMMAND );
        EnableMenuItem( hmenuPopup, VIEWER_EDIT_OPTIONS,
            ( IsIconic( hwndPicture ) ) ? MF_GRAYED: MF_ENABLED );
        EnableMenuItem( hmenuPopup, VIEWER_EDIT_CANCELSEL,
            ( IsRectEmpty( &pPictureData->qtoleOptions.rcSelection ))?
            MF_GRAYED: MF_ENABLED );
    }
    else if( nPopupIndex == MENU_IMAGE_POS ) {
        CheckMenuItem( hmenuPopup, VIEWER_IMAGE_HALFSIZE,
            ( pPictureData->zsZoomScroll.wCurZoomIndex ==
            VIEWER_IMAGE_HALFSIZE) ? MF_CHECKED: MF_UNCHECKED );
        CheckMenuItem( hmenuPopup, VIEWER_IMAGE_NORMALSIZE,
            ( pPictureData->zsZoomScroll.wCurZoomIndex ==
            VIEWER_IMAGE_NORMALSIZE) ? MF_CHECKED: MF_UNCHECKED );
        CheckMenuItem( hmenuPopup, VIEWER_IMAGE_DOUBLESIZE,
            ( pPictureData->zsZoomScroll.wCurZoomIndex ==
            VIEWER_IMAGE_DOUBLESIZE) ? MF_CHECKED: MF_UNCHECKED );
    }

    return 0L;
}



// Function: SetOptionsDefaults - Set option defaults
// --------------------------------------------------------------------
// Parameters: HWND             hwndPicture      Picture hwnd
//             NPPICTUREDATA    pPictureData     -> picture data struct
//
// Returns:    VOID
// --------------------------------------------------------------------
static VOID NEAR SetOptionsDefaults
                         ( HWND hwndPicture, NPPICTUREDATA pPictureData )

{ // Preset copy struct parameters that do not change
    pPictureData->qtoleOptions.lStructSize   = sizeof( QTOLE_OPTIONSPICTURE );
    pPictureData->qtoleOptions.lVersion      = VERSION_1;
    pPictureData->qtoleOptions.wObjectType   = PICTURE_OBJECT;
    pPictureData->qtoleOptions.hwndObject    = hwndPicture;

    pPictureData->qtoleOptions.phPicture     = pPictureData->phPicture;

    // Query qtw.ini for defaults
    ViewerGetDefaultOptions( &pPictureData->qtoleOptions );

    lstrcpy( pPictureData->qtoleOptions.szCaption, pPictureData->szPictureName );
    lstrcat( pPictureData->qtoleOptions.szCaption, pPictureData->szPictureExt );

    return;
}


// Function: UpdatePictForOptions - Sets the picture according to values set
//                                  in options struct
// --------------------------------------------------------------------
// Parameters: HWND          hwndPicture      Picture window handle
//             NPPICTUREDATA pPictureData     Picture data structure
//             BOOL          bCalledByOLE     TRUE if called by ole callback funct.
//
// Returns:    None
// --------------------------------------------------------------------
VOID FAR UpdatePictForOptions
    ( HWND hwndPicture, NPPICTUREDATA pPictureData, BOOL bCalledByOLE )

{
    LPQTOLE_OPTIONSPICTURE  lpOptions;
    POINT                   ptUL;
    WORD                    wZoomIndex;

    lpOptions = &pPictureData->qtoleOptions;
    if( lpOptions->bZoomHalf )
        SendMessage( hwndPicture, WM_COMMAND, VIEWER_IMAGE_HALFSIZE, 0L );
    else if( lpOptions->bZoomNormal )
        SendMessage( hwndPicture, WM_COMMAND, VIEWER_IMAGE_NORMALSIZE, 0L );
    else if( lpOptions->bZoomDouble )
        SendMessage( hwndPicture, WM_COMMAND, VIEWER_IMAGE_DOUBLESIZE, 0L );
    else if( bCalledByOLE ) {
        wZoomIndex = ViewerQueryZoomIndex( lpOptions->wZoomCurrent );
        ZoomPicture( hwndPicture, wZoomIndex );

        if( !IsRectEmpty( &lpOptions->rcSelection )) {
            ptUL = *(LPPOINT) &lpOptions->rcSelection.left;
            ptUL.x = MulDiv( ptUL.x, (int) lpOptions->wZoomCurrent, 100 );
            ptUL.y = MulDiv( ptUL.y, (int) lpOptions->wZoomCurrent, 100 );

            ProcessHorzScroll( hwndPicture, SB_THUMBPOSITION, ptUL.x );
            ProcessVertScroll( hwndPicture, SB_THUMBPOSITION, ptUL.y );

            InvalidateRect( hwndPicture, NULL, TRUE );
        }
    }

    return;
}


// Function: PopulateOptionsStruct - Populated the options structure
// --------------------------------------------------------------------
// Parameters: HWND          hwndPicture      Picture window handle
//             NPPICTUREDATA pPictureData     Picture data structure
//
// Returns:    None
// --------------------------------------------------------------------
static VOID NEAR PopulateOptionsStruct
                      ( HWND hwndPicture, NPPICTUREDATA pPictureData )

{ // Update these each time in case they get NULLed somewhere
    pPictureData->qtoleOptions.hwndObject = hwndPicture;
    pPictureData->qtoleOptions.phPicture  = pPictureData->phPicture;

    if( pPictureData->qtoleOptions.bZoomHalf )
        pPictureData->qtoleOptions.wZoomCurrent = 50;
    else if( pPictureData->qtoleOptions.bZoomNormal )
        pPictureData->qtoleOptions.wZoomCurrent = 100;
    else if( pPictureData->qtoleOptions.bZoomDouble )
        pPictureData->qtoleOptions.wZoomCurrent = 200;
    else
        pPictureData->qtoleOptions.wZoomCurrent =
        ViewerQueryZoomMultiplier
        ( pPictureData->zsZoomScroll.wCurZoomIndex );
    return;
}


//  The remaining functions are the query functions called by other modules

// Function: ViewerQueryActivePictureName - Query name of active picture
// --------------------------------------------------------------------
// Parameters: LPSTR      lpBuffer       string buffer
//
// Returns:    LPSTR      lpBuffer       Name of active picture
// --------------------------------------------------------------------
LPSTR FAR ViewerQueryActivePictureName( LPSTR lpBuffer )

{
    HWND            hwndPicture;  // Handle to active window
    NPPICTUREDATA   pPictureData; // -> picture data struct

    hwndPicture = (HWND) SendMessage
        ( ViewerQueryClientWindow(), WM_MDIGETACTIVE, 0, 0L );
    *lpBuffer = 0;
    if( pPictureData = (NPPICTUREDATA) GetWindowWord( hwndPicture, 0 ))
        lstrcpy( lpBuffer, pPictureData->szPictureName );

    return lpBuffer;
}

