
/****************************************************************************
*
*                   SciTech Multi-platform Graphics Library
*
*  ========================================================================
*
*    The contents of this file are subject to the SciTech MGL Public
*    License Version 1.0 (the "License"); you may not use this file
*    except in compliance with the License. You may obtain a copy of
*    the License at http://www.scitechsoft.com/mgl-license.txt
*
*    Software distributed under the License is distributed on an
*    "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
*    implied. See the License for the specific language governing
*    rights and limitations under the License.
*
*    The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
*
*    The Initial Developer of the Original Code is SciTech Software, Inc.
*    All Rights Reserved.
*
*  ========================================================================
*
* Language:     ANSI C
* Environment:  Any
*
* Description:  Mouse cursor resource loading/unloading routines. Cursors
*               are stored on disk in standard Windows .CUR cursor files.
*
****************************************************************************/

#include "mgl.h"

/*--------------------------- Global Variables ----------------------------*/

cursor_t _MGL_def_cursor = {    /* Standard arrow cursor                */
  { 1,
    {
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x7F,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,
    0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    },
    {
    0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,
    0x00,0xFF,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0xC7,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x83,0xC0,0x00,0x00,0x00,0x00,0x00,
    0x00,0x03,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    },
    8,0 }
    };

/*------------------------- Implementation --------------------------------*/

/****************************************************************************
RETURNS:
Pointer to the default cursor image.
****************************************************************************/
cursor_t * MGLAPI _MGL_getDefCursor(void)
{
    return &_MGL_def_cursor;
}

/****************************************************************************
PARAMETERS:
h           - Cursor header info to fill in
f           - Open binary file to read cursor from
dwOffset    - Offset to start of info in binary file

RETURNS:
True on success, false on failure

REMARKS:
Attempts to open the specified cursor file and read the cursor header
information. If the header information is invalid, we close the file and
return NULL.
****************************************************************************/
static ibool openCursorFileExt(
    winCURSORHEADER *h,
    FILE *f,
    ulong dwOffset)
{
    size_t              size;
    winCURSORDIRENTRY   *e = &h->chEntries[0];

    /* Read in the h record and verify the cursor file. We only accept
     * valid files containing a single 32x32 monochrome cursor.
     */
    __MGL_result = grOK;
    __MGL_fseek(f,dwOffset,SEEK_SET);
    size = __MGL_fread(h,1,sizeof(winCURSORHEADER),f);
    if (size != sizeof(winCURSORHEADER) || getLEShort(h->chReserved) != 0
            || getLEShort(h->chType) != 2 || getLEShort(h->chCount) != 1) {
        __MGL_result = grInvalidCursor;
        return false;
        }

    /* We can handle both 32x32 and 64x64 cursors, although I don't know
     * any Windows resource editors that can spit out 64x64 cursors (but
     * we can load them!).
     */
    if (e->bWidth == 32 && e->bHeight == 32)
        return true;
    if (e->bWidth == 64 && e->bHeight == 64)
        return true;
    __MGL_result = grInvalidCursor;
    return false;
}

/****************************************************************************
PARAMETERS:
h           - Cursor header info to fill in
cursorName  - Name of the cursor file to load

RETURNS:
Handle to the opened file, NULL on error.

REMARKS:
Attempts to open the specified cursor file and read the cursor header
information. If the header information is invalid, we close the file and
return NULL.
****************************************************************************/
static FILE *openCursorFile(
    winCURSORHEADER *h,
    const char *cursorName)
{
    FILE    *f;

    __MGL_result = grOK;
    f = _MGL_openFile(MGL_CURSORS, cursorName, "rb");
    if (f == NULL) {
        __MGL_result = grCursorNotFound;
        return NULL;
        }
    if (!openCursorFileExt(h,f,0)) {
        __MGL_fclose(f);
        return NULL;
        }
    return f;
}

/****************************************************************************
PARAMETERS:
h           - Cursor header info to fill in
cursorName  - Name of the cursor file to load

RETURNS:
Pointer to the loaded cursor, NULL on error.

REMARKS:
Routine to do the actual loading of the cursor file from disk. Assumes
the file pointer is located at the start of the cursor image.
****************************************************************************/
static cursor_t * LoadCursor32x32(
    winCURSORHEADER *h,
    FILE *f)
{
    cursor_t    *cursor;
    ulong       XORMask[32],ANDMask[32];
    uchar       *SrcANDMask,*SrcXORMask;
    uchar       *DstANDMask,*DstXORMask;
    int         i,j,ofs32,ofs64;

    /* Allocate memory and read in the cursor */
    if ((cursor = PM_malloc(sizeof(mono_cursor_t))) == NULL) {
        FATALERROR(grNoMem);
        return NULL;
        }

    /* Save the hotspot values */
    cursor->m.bitsPerPixel = 1;
    cursor->m.xHotSpot = getLEShort(h->chEntries[0].wXHotSpot);
    cursor->m.yHotSpot = getLEShort(h->chEntries[0].wYHotSpot);

    /* Skip past the BITMAPINFOHEADER and color table */
    __MGL_fseek(f,48,SEEK_CUR);
    __MGL_fread(XORMask,1,sizeof(XORMask),f);
    __MGL_fread(ANDMask,1,sizeof(ANDMask),f);

    /* Swap the ordering of the bitmaps, since DIB's start in the lower
     * left corner, while we want them to start in the upper right corner
     */
    for (i = 0; i < 16; i++) {
        SWAP(ANDMask[i],ANDMask[31-i]);
        SWAP(XORMask[i],XORMask[31-i]);
        }

    /* Invert the AND mask so that we can simply draw to punch a hole */
    for (i = 0; i < 32; i++)
        ANDMask[i] ^= 0xFFFFFFFF;

    /* Finally expand the image to the 64x64 internal cursor format */
    SrcXORMask = (uchar*)XORMask;
    SrcANDMask = (uchar*)ANDMask;
    DstXORMask = (uchar*)&cursor->m.xorMask;
    DstANDMask = (uchar*)&cursor->m.andMask;
    for (j = 0; j < 32; j++) {
        ofs32 = j * 4;
        ofs64 = j * 8;
        for (i = 0; i < 4; i++) {
            DstXORMask[ofs64+i] = SrcXORMask[ofs32+i];
            DstANDMask[ofs64+i] = SrcANDMask[ofs32+i];
            }
        /* Pad all remaining bytes in scanline to transparent */
        for (;i < 8; i++) {
            DstXORMask[ofs64+i] = 0;
            DstANDMask[ofs64+i] = 0;
            }
        }

    /* Pad all remaining scanlines to transparent */
    if (j < 64) {
        memset(&DstXORMask[j*8],0,(64 - j) * 8);
        memset(&DstANDMask[j*8],0,(64 - j) * 8);
        }

    /* Return the loaded cursor */
    return cursor;
}

/****************************************************************************
PARAMETERS:
h           - Cursor header info to fill in
cursorName  - Name of the cursor file to load

RETURNS:
Pointer to the loaded cursor, NULL on error.

REMARKS:
Routine to do the actual loading of the cursor file from disk. Assumes
the file pointer is located at the start of the cursor image.
****************************************************************************/
static cursor_t * LoadCursor64x64(
    winCURSORHEADER *h,
    FILE *f)
{
    cursor_t    *cursor;
    ulong       *XORMask,*ANDMask;
    int         i;

    /* Allocate memory and read in the cursor */
    if ((cursor = PM_malloc(sizeof(mono_cursor_t))) == NULL) {
        FATALERROR(grNoMem);
        return NULL;
        }

    /* Save the hotspot values */
    cursor->m.bitsPerPixel = 1;
    cursor->m.xHotSpot = getLEShort(h->chEntries[0].wXHotSpot);
    cursor->m.yHotSpot = getLEShort(h->chEntries[0].wYHotSpot);

    /* Skip past the BITMAPINFOHEADER and color table */
    __MGL_fseek(f,48,SEEK_CUR);
    __MGL_fread(&cursor->m.xorMask,1,sizeof(cursor->m.xorMask),f);
    __MGL_fread(&cursor->m.andMask,1,sizeof(cursor->m.andMask),f);

    /* Swap the ordering of the bitmaps, since DIB's start in the lower
     * left corner, while we want them to start in the upper right corner
     */
    ANDMask = (ulong*)cursor->m.andMask;
    XORMask = (ulong*)cursor->m.xorMask;
    for (i = 0; i < 64; i++) {
        SWAP(ANDMask[i*2],  ANDMask[(63-i)*2]);
        SWAP(ANDMask[i*2+1],ANDMask[(63-i)*2+1]);
        SWAP(XORMask[i*2],  XORMask[(63-i)*2]);
        SWAP(XORMask[i*2+1],XORMask[(63-i)*2+1]);
        }

    /* Invert the AND mask so that we can simply draw to punch a hole */
    for (i = 0; i < 512; i++)
        cursor->m.andMask[i] ^= 0xFF;
    return cursor;
}

/****************************************************************************
DESCRIPTION:
Load a cursor file from disk.

HEADER:
mgraph.h

PARAMETERS:
cursorName  - Name of cursor file to load

RETURNS:
Pointer to loaded cursor file, NULL on error.

REMARKS:
Locates the specified cursor file and loads it into memory.  MGL can load any
Windows 3.x style cursor files. Consult the Windows SDK documentation for the
format of Windows cursor files.

When MGL is searching for cursor files it will first attempt to find the files just by
using the filename itself. Hence if you wish to look for a specific cursor file, you
should pass the full pathname to the file that you are interested in. If the filename is
a simple relative filename (i.e. "MYCURS.CUR"), MGL will then search in the
CURSORS directory relative to the path specified in mglpath variable that was
passed to MGL_init. As a final resort MGL will also look for the files in the
CURSORS directory relative to the MGL_ROOT environment variable.

If the cursor file was not found, or an error occurred while reading the cursor file,
this function will return NULL. You can check the MGL_result error code to
determine the cause.

SEE ALSO:
MGL_unloadCursor, MGL_availableCursor, MS_setCursor, MGL_loadCursorExt
****************************************************************************/
cursor_t * MGLAPI MGL_loadCursor(
    const char *cursorName)
{
    cursor_t        *cursor;
    FILE            *f;
    winCURSORHEADER h;

    if ((f = openCursorFile(&h,cursorName)) == NULL)
        return NULL;
    if (h.chEntries[0].bWidth == 32 && h.chEntries[0].bHeight == 32)
        cursor = LoadCursor32x32(&h,f);
    else
        cursor = LoadCursor64x64(&h,f);
    fclose(f);
    if (!cursor)
        return NULL;
    return cursor;
}

/****************************************************************************
DESCRIPTION:
Load a cursor file from disk from an opened file.

HEADER:
mgraph.h

PARAMETERS:
f           - Open file to read cursor from (binary mode)
dwOffset    - Offset to the start of the cursor file
dwSize      - Size of the file

RETURNS:
Pointer to the loaded cursor file

REMARKS:
This function is the same as MGL_loadCursor, however it works with a
previously opened file. This allows you to create your own large files with
multiple files embedded in them.

SEE ALSO:
MGL_loadCursor
****************************************************************************/
cursor_t * MGLAPI MGL_loadCursorExt(
    FILE *f,
    ulong dwOffset,
    ulong dwSize)
{
    winCURSORHEADER h;

    if (!openCursorFileExt(&h,f,dwOffset))
        return NULL;
    (void)dwSize;
    if (h.chEntries[0].bWidth == 32 && h.chEntries[0].bHeight == 32)
        return LoadCursor32x32(&h,f);
    else
        return LoadCursor64x64(&h,f);
}

/****************************************************************************
DESCRIPTION:
Determines if the specified cursor file is available for use.

HEADER:
mgraph.h

PARAMETERS:
cursorName  - Name of cursor file to check for

RETURNS:
True if the cursor file exists, false if not.

REMARKS:
Attempt to locate the specified mouse cursor, and verify that it is available for use.
See MGL_loadCursor for more information on the algorithm that MGL uses when
searching for mouse cursor files on disk.

SEE ALSO:
MGL_loadCursor
****************************************************************************/
ibool MGLAPI MGL_availableCursor(
    const char *cursorName)
{
    FILE            *f;
    winCURSORHEADER h;

    __MGL_result = grOK;
    if ((f = openCursorFile(&h,cursorName)) == NULL)
        return false;
    __MGL_fclose(f);
    return true;
}

/****************************************************************************
DESCRIPTION:
Unloads a cursor file from memory.

HEADER:
mgraph.h

PARAMETERS:
cursor  - Pointer to cursor to unload

REMARKS:
Unloads the specified cursor file from memory, and frees up all the system
resources associated with this cursor.

SEE ALSO:
MGL_loadCursor
****************************************************************************/
void MGLAPI MGL_unloadCursor(
    cursor_t *cursor)
{
    if (cursor != MGL_DEF_CURSOR)
        PM_free(cursor);
}
