
/*
 *@@sourcefile shapewin.c:
 *      contains helper functions for PM windows which do not have a
 *      rectangular shape, but are defined using GPI regions.
 *
 *      This file is new with V0.85.
 *
 *      Function prefixes:
 *      --  shp*    shape window functions
 *
 *      This file consists of two parts:
 *
 *  *** Part 1 ***
 *
 *  The code in Part 1 implements the "Shape" window. This is used
 *  to display a bitmap on the screen with transparency.
 *
 *  Really, it is not the "Shape" window which displays the bitmap.
 *  Since PM can only handle rectangular windows, as many subwindows
 *  as required are created, which are only managed by the "Shape"
 *  window.
 *
 *  If you are not using the frame window functions in Part 2, the
 *  shape window class must be registered like this (the declarations
 *  are in shapein.h):
 *
 +      WinRegisterClass(
 +                     hab,                // anchor block handle
 +                     SHAPE_WINDOW_CLASS, // class name (shapewin.h)
 +                     fnwpShapeWindow,    // window procedure (in this file)
 +                     0L,                 // default window style
 +                     sizeof(PVOID)) ;    // class data size
 *
 *  You can then create shape windows using WinCreateWindow():
 +      hwndShape = WinCreateWindow(
 +                     hwndParent,      // see remark (2)
 +                     SHAPE_WINDOW_CLASS, // class name (shapewin.h)
 +                     pszName,         // doesn't matter
 +                     flStyle,         // can be 0
 +                     x, y, cx, cy,    // can all be 0; shape window
 +                                      // size depends on bitmap
 +                     hwndOwner,       // see remark (2)
 +                     hwndInsertBehind, // should be HWND_TOP
 +                     id,
 +                     pCtlData,        // *SHPCTLDATA, see remark (1)
 +                     pPresParams);    // NULL
 *
 *  Remarks:
 *
 *  1)  Shape windows _require_ a SHPCTLDATA structure for input with
 *      the pCtlData parameter of WinCreateWindow. This structure
 *      must contain the following:
 +          SHORT   cx, cy ;       // bitmap size
 +          HPS     hpsMask;       // HPS with bitmap selected into
 +          HPS     hpsDraw;       // HPS used for drawing the bitmap
 *
 *      Note that hpsMask determines the drawing mask of the bitmap,
 *      while hpsDraw determines the bitmap to be drawn. You can set
 *      both to the same HPS, but you can also use a different bitmap
 *      for the "shape" (in hpsMask).
 *
 *      You _must_ select the bitmap to be displayed into the HPS's,
 *      using GpiSetBitmap, or nothing will work. shpLoadBitmap
 *      in Part 2 of this file offers you a one-shot function for
 *      loading a bitmap either from resources or a PM 1.3 *.BMP
 *      file.
 *
 *      Transparency is determined according to the most lower-left
 *      pixel of the bitmap in hpsMask. That is, all bitmap pixels
 *      which have the same color as pixel (0, 0) will be made
 *      transparent.
 *
 *      During window creation, the shape window analyzes the bitmap
 *      which was selected into hpsMask and creates lots of
 *      rectangular PM windows as neccessary. This is dependent on
 *      the "transparency" pixels of the bitmap.
 *
 *      There is no limit for the size of the bitmap (in the HPS's).
 *      But you should be very careful with large bitmaps, because
 *      this can block the system forever. Shape windows are real
 *      CPU hogs. The number of PM subwindows created depends on the
 *      size of the bitmap and on the transparency. The calculations
 *      are line-based:
 *      --  each line is examined, and a PM window is created for
 *          each line of the input bitmap;
 *      --  if this line contains "transparency" pixels, more PM
 *          windows are created accordingly for that line.
 *      --  However, if several vertically adjacent subwindows have
 *          the same left and right coordinates, they are combined
 *          into one window.
 *
 *      As a result, the more single transparent pixels you have,
 *      the more windows need to be created. The more rectangular
 *      transparent areas you have, the less windows need to be
 *      created.
 *
 *  2)  Shape window requires both a parent and an owner window. As
 *      always, the parent window determines the visibility, while
 *      the owner window requires a more abstract relationship. With
 *      shape windows, the owner window is important because all the
 *      input messages (for mouse and keyboard input) are forwarded
 *      to the owner.
 *
 *      So it's best to set the parent to HWND_DESKTOP and specify
 *      a frame window for the owner. If you don't already have a
 *      frame window in your application, you can create an invisible
 *      frame window just for message handling. That's what the shp*
 *      functions in Part 2 of this file do (Akira Hatakeyama calls
 *      this the "Voodoo" feature).
 *
 *      Never use the shape window as a top-level window.
 *
 *      Note that the keyboard and mouse input messages which are
 *      forwarded to the owner contain mouse coordinates relative
 *      to the rectangular shape subwindow which actually received
 *      the message, not relative to the frame window. So you better
 *      determine the real mouse position using WinQueryPointerPos().
 *
 *  Note that the size of the shape window is defined once upon
 *  creation and cannot be changed later because this would conflict
 *  with all the subwindows which were created. For the same reason,
 *  you cannot alter the bitmap of the shape window after creation.
 *  You must create a second shape bitmap for that.
 *  Thus all the size parameters in WinSetWindowPos are swallowed and
 *  ignored.
 *
 *  *** Part 2 ***
 *
 *  This implements a proper invisible frame window for displaying
 *  shaped bitmap windows directly on the desktop.
 *  See shpLoadBitmap, shpFreeBitmap, and shpCreateWindows for more.
 *
 *@@include #define INCL_GPIBITMAPS // or INCL_GPIPRIMITIVES
 *@@include #include <os2.h>
 *@@include #include "shapewin.h"
 */

/*
 *  Credits:
 *      1)  Functions for shaped windows.
 *          This is based on the ShapeWin library 1.01 (SHAPEWIN.ZIP at
 *          Hobbes), (C) 1998, 1999 Software Research Associates, Inc.
 *          This has been written by Akira Hatakeyama (akira@sra.co.jp).
 *          Updated by Takayuki Suwa (jjsuwa@ibm.net).
 *          The original was placed under the GPL.
 *
 *          Changes made by Ulrich M”ller (February 1999):
 *          --  some speedup (marked with *UM)
 *          --  renamed and commented out almost all of the functions
 *              and structures to make their use more lucid.
 *
 *      2)  Functions for easily creating shape frame windows from a
 *          given HBITMAP. This has been written by me, after getting
 *          some inspiration from the sample programs in SHAPEWIN.ZIP.
 *
 *      Copyright (C) 1998-99
 *                  Ulrich M”ller,
 *                  Akira Hatakeyama,
 *                  Takayuki Suwa.
 *      This file is part of the XFolder source package.
 *      XFolder is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XFolder main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 */

#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_WIN
#define INCL_WINSYS
#define INCL_GPILOGCOLORTABLE
#define INCL_GPIPRIMITIVES
#include <os2.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "gpih.h"
#include "shapewin.h"

/* ******************************************************************
 *                                                                  *
 *   Part 1: Shape window functions                                 *
 *                                                                  *
 ********************************************************************/

// forward declarations for structures, because
// the structures are referenced from each other
typedef struct  _SHPWINCTLDATA  *PSHPWINCTLDATA;
typedef struct  _SHPREGION  *PSHPREGION;

/*
 * SHPWINCTLDATA:
 *      internal shape window control data.
 *      This is the shape-window-internal structure
 *      to allow the shape window to manage itself.
 *      This is created in WM_CREATE with the data
 *      passed in the mp1 and mp2 parameters
 *      (CREATESTRUCT and SHPCTLDATA).
 *      This is stored in a shape window's window words.
 */

typedef struct _SHPWINCTLDATA
{
    // common window parameters
    // (copied from CREATESTRUCT in WM_CREATE)
    HWND        hwndShape ;    // main shape window handle
    HWND        hwndParent;    // shape window's parent (should be HWND_DESKTOP)
    HWND        hwndOwner ;    // shape window's owner, to whom we'll forward all
                               // all the input messages
    USHORT      id        ;    // window ID
    SHORT       x         ;    // x - horz. position
    SHORT       y         ;    // y - vert. position
    SHORT       cx        ;    // horz. size
    SHORT       cy        ;    // vert. size
    PSZ         pszText   ;    // window text

    // additional window parameters
    HPS         hpsDraw;       // the input HPS with the bitmap
    ULONG       nRegion;       // number of "regions"
    PSHPREGION  aRegion;       // array of "regions"

} SHPWINCTLDATA;

/*
 * SHPREGION:
 *      this is the structure for shape window
 *      "regions". Note that has nothing to do
 *      with GPI regions, but is simply a structure
 *      for each rectangular area in the shape
 *      window's bitmap which can be displayed
 *      as a rectangular PM window.
 */

typedef struct _SHPREGION
{
    PSHPWINCTLDATA   pCtrl;   // link to parent
    HWND        hwnd ;         // drawing window
    RECTL       rect ;         // rectangle of sub-bitmap
} SHPREGION;

/*
 * SHPSPAN:
 *      this is a temporary structure used during
 *      the creation of SHPREGION's from the
 *      input bitmap.
 */

typedef struct _SHPSPAN {
    ULONG   nSize   ;
    ULONG   nUsed   ;
    RECTL   aSpan[1];
} SHPSPAN, *PSHPSPAN;

// the following defines how many SHPSPAN structures
// will be initially created and afterwards for span
// expansions by spanExpand
#define SPAN_ALLOC_STEP    1024

/*
 * spanFree:
 *
 */

static void spanFree(PSHPSPAN pSpan)
{
    if (pSpan != NULL) {
        free(pSpan);
    }
}

/*
 * spanCreate:
 *      this creates SPAN_ALLOC_STEP SHPSPAN structures.
 */

static PSHPSPAN spanCreate(void)
{
    PSHPSPAN pSpan;
    int     len  ;

    len = sizeof(SHPSPAN) + sizeof(RECTL) * SPAN_ALLOC_STEP;

    if ((pSpan = (PSHPSPAN) malloc(len)) == NULL) {
        return NULL;
    }
    memset(pSpan, 0, len);
    pSpan->nSize = SPAN_ALLOC_STEP;
    pSpan->nUsed = 0   ;
    return pSpan;
}

/*
 * spanExpand:
 *      this expands the memory used for the SHPSPAN
 *      structures if more spans need to be appended.
 *      Another SPAN_ALLOC_STEP SHPSPAN structures are created.
 */

static PSHPSPAN spanExpand(PSHPSPAN pOld)
{
    PSHPSPAN pNew;
    int     len ;

    // TRACE("spanExpand\n");

    len = sizeof(SHPSPAN) + sizeof(RECTL) * (pOld->nSize + SPAN_ALLOC_STEP);

    if ((pNew = (PSHPSPAN) malloc(len)) == NULL) {
        return NULL;
    }
    memset(pNew, 0, len);
    pNew->nSize = pOld->nSize + SPAN_ALLOC_STEP;
    pNew->nUsed = pOld->nUsed;
    memcpy(pNew->aSpan, pOld->aSpan, sizeof(RECTL) * pOld->nUsed);
    spanFree(pOld);

    return pNew;
}

/*
 * spanAppend:
 *      this adds a new SHPSPAN to the list of spans.
 *
 */

static PSHPSPAN spanAppend(PSHPSPAN pSpan,
                           int y,       // bottom y; top y = y+1
                           int x1,      // left x
                           int x2)      // right x
{
    int     i;
    PRECTL  p;
    PSHPSPAN pNew;

    // TRACE("spanAppend %d (%d %d)\n", y, x1, x2);

    /*
     * check if continuous one
     */

    for (i = 0; i < pSpan->nUsed; i++) {
        p = &pSpan->aSpan[i];
        if (p->yTop == y && p->xLeft == x1 && p->xRight == x2)
        {
            p->yTop += 1;
            return pSpan;      // merged
        }
    }

    /*
     * if not enough space, expand
     */

    if ((pSpan->nUsed + 1) >= pSpan->nSize)
    {
        if ((pNew = spanExpand(pSpan)) == NULL)
            return NULL;

        pSpan = pNew;
    }

    /*
     * append a rectangle
     */

    pSpan->aSpan[pSpan->nUsed].yTop = y + 1;
    pSpan->aSpan[pSpan->nUsed].yBottom = y ;
    pSpan->aSpan[pSpan->nUsed].xLeft   = x1;
    pSpan->aSpan[pSpan->nUsed].xRight  = x2;
    pSpan->nUsed += 1;

    return pSpan;
}

#ifdef  SHPDEBUG

    /*
     * ptisin:
     *
     */

    static BOOL ptisin(PSHPSPAN pSpan, int x, int y)
    {
        int     i;

        for (i = 0; i < pSpan->nUsed; i++)
        {
            if (y >= pSpan->aSpan[i].yTop) {
                continue;
            }
            if (y < pSpan->aSpan[i].yBottom) {
                continue;
            }
            if (x < pSpan->aSpan[i].xLeft) {
                continue;
            }
            if (x >= pSpan->aSpan[i].xRight) {
                continue;
            }
            return TRUE;
        }
        return FALSE;
    }

    /*
     * dumpSpan:
     *
     */

    static void dumpSpan(PSHPSPAN pSpan)
    {
        int     i, maxx, maxy, x, y;

        TRACE("dumpSpan %d\n", pSpan->nUsed);

        maxx = maxy = 0;

        for (i = 0; i < pSpan->nUsed; i++) {
            if (pSpan->aSpan[i].yTop > maxy) {
            maxy = pSpan->aSpan[i].yTop;
        }
        if (pSpan->aSpan[i].xRight > maxx) {
            maxx = pSpan->aSpan[i].xRight;
        }
        }

        for (y = maxy - 1; y >= 0; y--) {
            printf("%03d : ", y);
            for (x = 0; x < maxx; x++) {
            if (ptisin(pSpan, x, y)) {
                printf("#");
            } else {
                printf("_");
            }
            }
        printf("\n");
        }
    }

#endif  // SHPDEBUG

/*
 * spanParseBitmap:
 *      this gets called from shapeBitmap2Regions when the
 *      shape region windows need to be created.
 *      This function does the actual bitmap analysis and
 *      creates a large number of SHPSPAN structures according
 *      to the bitmaps, which can later be turned into
 *      PM windows.
 *      This happens during the processing of WM_CREATE in
 *      fnwpShapeWindow.
 *      CPU usage: relatively low. Maybe 5% of the whole creation
 *      procedure.
 */

static PSHPSPAN spanParseBitmap(HPS hps, PBITMAPINFOHEADER2 bmih2)
{
    int     blen, hlen ;
    PUCHAR          buf;
    PBITMAPINFO2    pbm;
    PSHPSPAN pSpan, pNew;
    int     x, y, k    ;
    LONG    first, color;
    BOOL    inspan;
    int     left  ;

    // TRACE("spanParseBitmap\n");

    hlen = sizeof(BITMAPINFO2) + sizeof(RGB) * 256;
    blen = ((bmih2->cBitCount * bmih2->cx + 31) / 32) * bmih2->cPlanes * 4;

    pbm = (PBITMAPINFO2) malloc(hlen);
    buf = (PUCHAR) malloc(blen);

    if (pbm == NULL || buf == NULL) {
        // TRACE("spanParseBitmap - failed to alloc %d %d\n", hlen, blen);
        if (pbm)
            free(pbm);
        if (buf)
            free(buf);
        return NULL;
    }
    memcpy(pbm, bmih2, sizeof(BITMAPINFOHEADER2));

    if ((pSpan = spanCreate()) == NULL)
    {
        // TRACE("spanParseBitmap - failed to make\n");
        free(pbm);
        free(buf);
        return NULL;
    }

    first = -1;

    for (y = 0; y < bmih2->cy; y++)
    {

        // TRACE("spanParseBitmap - scan line %d\n", y); fflush(stdout);

        GpiQueryBitmapBits(hps, y, 1, buf, pbm);

        for (x = 0, inspan = FALSE; x < bmih2->cx; x++)
        {
            k = x * 3;
            color = ((buf[k] << 16) | (buf[k+1] << 8) | buf[k+2]);

            if (first < 0) {
                // first iteration: get the very first color (that
                // is the pixel at (0, 0)), with which we will
                // compare the others to determine the mask
                first = color;
            }

            if (inspan == FALSE && color != first)
            {
                inspan = TRUE;
                left = x;
            }
            else if (inspan == TRUE && color == first)
            {
                // transparent color found:
                // create new span
                inspan = FALSE;
                if ((pNew = spanAppend(pSpan, y, left, x)) != NULL)
                {
                    pSpan = pNew;
                } else {
                    // TRACE("spanParseBitmap - failed to extend\n");
                    break;
                }
            }
        }
        if (inspan == TRUE) {
                if ((pNew = spanAppend(pSpan, y, left, x)) != NULL) {
                pSpan = pNew;
            } else {
                // TRACE("spanParseBitmap - failed to extend\n");
                    break;
            }
        }
    }

#ifdef  SHPDEBUG
    dumpSpan(pSpan);
#endif

    return pSpan;
}

/*
 * shapeBitmap2Regions:
 *      this gets called from shapeWMCreate (WM_CREATE) to have a
 *      drawing region created from the bitmap which has been
 *      selected into the given HPS.
 */

static int shapeBitmap2Regions(PSHPWINCTLDATA pCtrl,  // in: shape control data
                         HPS hpsMask)      // in: HPS with selected bitmap
{
    HAB     hab;
    HDC     hdc;
    HPS     hps;
    SIZEL   siz;
    HBITMAP hbm;
    BITMAPINFOHEADER2   bmi;
    POINTL  apt[3];
    PSHPSPAN     pSpan;
    PSHPREGION   pRegn;
    int         i    ;

    // TRACE("shapeBitmap2Regions\n");

    hab = WinQueryAnchorBlock(pCtrl->hwndShape);

    /*
     * Create Memory DC & HPS
     */

    hdc = DevOpenDC(hab, OD_MEMORY, "*", 0, NULL, NULLHANDLE);
    siz.cx = siz.cy = 0;
    hps = GpiCreatePS(hab, hdc, &siz,
            PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC);

    /*
     * Create Bitmap and relate to memory PS
     */

    memset(&bmi, 0, sizeof(bmi));

    bmi.cbFix = sizeof(BITMAPINFOHEADER2);
    bmi.cx = pCtrl->cx;
    bmi.cy = pCtrl->cy;
    bmi.cPlanes       = 1 ;
    bmi.cBitCount     = 24;
    bmi.ulCompression = 0 ;
    bmi.cclrUsed      = 0 ;
    bmi.cclrImportant = 0 ;

    hbm = GpiCreateBitmap(hps, &bmi, 0, NULL, NULL);

    GpiSetBitmap(hps, hbm);

    /*
     * Copy in Mask Pattern
     */

    // TRACE("shapeBitmap2Regions - copyin %d x %d mask pattern\n", bmi.cx, bmi.cy);

    apt[0].x = 0;          // Target
    apt[0].y = 0;
    apt[1].x = bmi.cx;
    apt[1].y = bmi.cy;
    apt[2].x = 0;          // Source
    apt[2].y = 0;

    if (GpiBitBlt(hps, pCtrl->hpsDraw, 3, apt, ROP_SRCCOPY, 0) == GPI_ERROR) {
        // TRACE("MakeRect - BitBlt Failed %08x, hdc %08x, hps %08x, hbm %08x\n",
                // WinGetLastError(hab), hdc, hps, hbm);
    }

    /*
     * Parse Mask Pattern
     */

    // call spanParseBitmap; this does the actual creation of the
    // "spans" by comparing all bitmap pixels to the (0, 0)
    // pixel
    if ((pSpan = spanParseBitmap(hps, &bmi)) != NULL)
    {
        if ((pRegn = (PSHPREGION) malloc(sizeof(SHPREGION) * pSpan->nUsed)) == NULL)
        {
        // TRACE("shapeBitmap2Regions - failed to alloc\n");
        } else
        {
            // mask created successfully:
            pCtrl->nRegion = pSpan->nUsed;
            pCtrl->aRegion = pRegn;

            for (i = 0; i < pSpan->nUsed; i++)
            {
                pRegn[i].pCtrl    = pCtrl;
                pRegn[i].hwnd = NULLHANDLE;
                pRegn[i].rect.xLeft   = pSpan->aSpan[i].xLeft  ;
                pRegn[i].rect.xRight  = pSpan->aSpan[i].xRight ;
                pRegn[i].rect.yTop    = pSpan->aSpan[i].yTop   ;
                pRegn[i].rect.yBottom = pSpan->aSpan[i].yBottom;
            }
        }
        spanFree(pSpan);
    }

    /*
     * dispose local resources
     */

    GpiDeleteBitmap(hbm);
    GpiDestroyPS(hps);
    DevCloseDC(hdc);

    return 0;
}

/*
 * regnDraw:
 *      this gets called upon receiving WM_PAINT in
 *      fnwpRegionSubwindow. We simply draw the
 *      subrectangle from the shape window's bitmap
 *      which corresponds to our region rectangle.
 */

static void regnDraw(HPS hps, PSHPREGION pRgn)
{
    POINTL      apt[3];

    apt[0].x = 0;
    apt[0].y = 0;
    apt[1].x = (pRgn->rect.xRight - pRgn->rect.xLeft);
    apt[1].y = (pRgn->rect.yTop - pRgn->rect.yBottom);
    apt[2].x = pRgn->rect.xLeft  ;
    apt[2].y = pRgn->rect.yBottom;
    GpiBitBlt(hps, pRgn->pCtrl->hpsDraw, 3, apt, ROP_SRCCOPY, 0);
}

/*
 *@@ fnwpRegionSubwindow:
 *      this is the window procedure for each of the
 *      shape window's rectangular subwindows.
 */

static MRESULT EXPENTRY fnwpRegionSubwindow(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    PSHPREGION   pRgn;
    HPS         hps ;

    pRgn = (PSHPREGION) WinQueryWindowPtr(hwnd, 0);

    switch (msg)
    {
        /*
         * WM_CREATE:
         *      store the SHPREGION pointer in our
         *      window words.
         */

        case WM_CREATE:
            // TRACE("WM_CREATE\n");
            pRgn = (PSHPREGION) PVOIDFROMMP(mp1);
            WinSetWindowPtr(hwnd, 0, (PVOID) pRgn);
        return (MRESULT) FALSE;

        /*
         * WM_PAINT:
         *      draw our subrectangle of the main bitmap.
         */

        case WM_PAINT:
            // TRACE("WM_PAINT (%d %d) (%d %d)\n", pRgn->rect.yBottom, pRgn->rect.xLeft, pRgn->rect.yTop, pRgn->rect.xRight);
            hps = WinBeginPaint(hwnd, NULLHANDLE, NULL);
            regnDraw(hps, pRgn);
            WinEndPaint(hps)  ;
        return (MRESULT) 0;

        case WM_MOUSEMOVE    :
        case WM_BUTTON1DOWN  :
        case WM_BUTTON1UP    :
        case WM_BUTTON1CLICK :
        case WM_BUTTON1DBLCLK:
        case WM_BUTTON2DOWN  :
        case WM_BUTTON2UP    :
        case WM_BUTTON2CLICK :
        case WM_BUTTON2DBLCLK:
        case WM_BUTTON3DOWN  :
        case WM_BUTTON3UP    :
        case WM_BUTTON3CLICK :
        case WM_BUTTON3DBLCLK:
        case WM_CHAR         :
        case WM_VIOCHAR      :
        case WM_BEGINDRAG    :
        case WM_ENDDRAG      :
        case WM_SINGLESELECT :
        case WM_OPEN         :
        case WM_CONTEXTMENU  :
        case WM_CONTEXTHELP  :
        case WM_TEXTEDIT     :
        case WM_BEGINSELECT  :
        case WM_ENDSELECT    :
            // forward all these to the shape window
            return WinSendMsg(pRgn->pCtrl->hwndShape, msg, mp1, mp2);
    }
    return WinDefWindowProc(hwnd, msg, mp1, mp2);
}

static BOOL    ShapeRegisteredRegion = FALSE;

/*
 * shapeRegions2Windows:
 *      this gets called from shapeWMCreate (WM_CREATE) to convert
 *      all the "regions" that have been created by shapeBitmap2Regions
 *      in pCtrl to individual rectangular PM windows.
 *      CPU usage: extremely high, because all the dull PM functions
 *      take so damn long. Calculating the "regions" isn't really
 *      difficult, but it's the PM windows which cause the CPU hog.
 */

static int shapeRegions2Windows(PSHPWINCTLDATA pCtrl)
{
    int         i   ;
    PSHPREGION   pRgn;
    ULONG       flStyle;

    // TRACE("shapeRegions2Windows %d regions\n", pCtrl->nRegion);

    // check if any "regions" exist at all
    if (pCtrl->nRegion == 0 || pCtrl->aRegion == NULL)
    {
        // TRACE("shapeRegions2Windows - no region to open\n");
        return -1;
    }

    if (ShapeRegisteredRegion == FALSE) {
        // first call: register "shape region" class
        WinRegisterClass(WinQueryAnchorBlock(pCtrl->hwndShape),
                        "ShapeWinRegion",
                        fnwpRegionSubwindow,
                        CS_PARENTCLIP
                            // use parent's clipping region instead
                            // of the one of the region window
                        | CS_CLIPSIBLINGS
                            // don't allow the subwindows to paint
                            // over siblings (ie. windows with the
                            // same parent); this is neccessary if
                            // HWND_DESKTOP is the parent
                        | CS_SYNCPAINT
                            // paint immediately
                        ,
                        sizeof(PVOID));
        ShapeRegisteredRegion = TRUE;
    }

    flStyle = 0;

    // now go thru the "regions" list
    for (i = 0, pRgn = pCtrl->aRegion;
         i < pCtrl->nRegion;
         i++, pRgn++)
    {
        // and create a window for each "region"
        pRgn->hwnd = WinCreateWindow(
                pCtrl->hwndParent,      // copy parent window from shape window
                            // changed (*UM)
                        // HWND_DESKTOP,           // Parent Window
                "ShapeWinRegion",       // Window Class
                NULL,                   // Window Text
                flStyle,                // Window Style
                (pCtrl->x + pRgn->rect.xLeft),              // x
                (pCtrl->y + pRgn->rect.yBottom),            // y
                (pRgn->rect.xRight - pRgn->rect.xLeft),     // cx
                (pRgn->rect.yTop - pRgn->rect.yBottom),     // cy
                pCtrl->hwndOwner,       // Owner Window
                HWND_TOP,               // Z-Order
                i,                      // Window ID
                pRgn,                   // Control Data
                NULL);                  // Pres. Param.
    }
    return 0;
}

/*
 * shapeFreeRegion - Dispose Region Window and Related Resources
 */

static int shapeFreeRegion(PSHPWINCTLDATA pCtrl)
{
    int         i   ;
    PSHPREGION   pRgn;

    for (i = 0, pRgn = pCtrl->aRegion; i < pCtrl->nRegion; i++, pRgn++) {
        WinDestroyWindow(pRgn->hwnd);
    }
    free(pCtrl->aRegion);

    return 0;
}

/*
 * shapeWMAdjustWindowPos:
 *      adjust the region windows' positions, visibility etc.
 *      by going thru the whole region windows list and
 *      repositioning all the windows.
 *      This gets called upon receiving WM_ADJUSTWINDOWPOS
 *      in fnwpShapeWindow.
 */

static int shapeWMAdjustWindowPos(PSHPWINCTLDATA pCtrl, PSWP pSwp)
{
    int         i   ;
    PSHPREGION   pRgn;
    ULONG       fl  ;
    HPS         hps ;

    PSWP pswpArray;

    // TRACE("shapeWMAdjustWindowPos - %d, %08x\n", pCtrl->nRegion, pSwp->fl);

    if (pCtrl->nRegion == 0 || pCtrl->aRegion == NULL)
    {
        // TRACE("ShpDrawRegion - no region to open\n");
        return -1;
    }

    if ((fl = pSwp->fl) & SWP_MOVE)
    {
        pCtrl->x = pSwp->x;
        pCtrl->y = pSwp->y;
    }

    pswpArray = (PSWP) malloc(sizeof(SWP) * pCtrl->nRegion);

    // go thru all the "regions" and adjust their sizes
    for (i = 0, pRgn = pCtrl->aRegion; i < pCtrl->nRegion; i++, pRgn++)
    {
        pswpArray[i].fl = fl;
        pswpArray[i].cy = (pRgn->rect.yTop - pRgn->rect.yBottom);
        pswpArray[i].cx = (pRgn->rect.xRight - pRgn->rect.xLeft);
        pswpArray[i].y = (pCtrl->y + pRgn->rect.yBottom);
        pswpArray[i].x = (pCtrl->x + pRgn->rect.xLeft);
        pswpArray[i].hwndInsertBehind = pSwp->hwndInsertBehind;
        pswpArray[i].hwnd = pRgn->hwnd;
    }

    // set all window positions at once
    WinSetMultWindowPos(WinQueryAnchorBlock(pCtrl->hwndShape),
                        &pswpArray[0],
                        pCtrl->nRegion);
    free((void*) pswpArray);

    // calling WinInvalidateRect is not neccessary,
    // because the windows' update regions are already
    // properly set by WinSetMultWindowPos (*UM)

    /* for (i = 0, pRgn = pCtrl->aRegion;
         i < pCtrl->nRegion;
         i++, pRgn++)
    {
        if (fl & (SWP_MOVE | SWP_ZORDER)) {
            WinInvalidateRect(pRgn->hwnd, NULL, FALSE);
        }
    } */
    return 0;
}

/*
 * shapeUpdateRegions:
 *      this gets called by fnwpShapeWindow upon receiving
 *      SHAPEWIN_MSG_UPDATE.
 */

static int shapeUpdateRegions(PSHPWINCTLDATA pCtrl, PRECTL pRect)
{
    RECTL       rect, intern;
    int         i   ;
    PSHPREGION   pRgn;
    HAB         hab ;
    HPS         hps ;

    // TRACE("shapeUpdateRegions\n");

    hab = WinQueryAnchorBlock(pCtrl->hwndShape);

    if (pRect == NULL) {
        rect.xLeft   = 0       ;
        rect.yBottom = 0       ;
        rect.xRight = pCtrl->cx;
        rect.yTop   = pCtrl->cy;
        pRect = &rect;
    }
    for (i = 0, pRgn = pCtrl->aRegion; i < pCtrl->nRegion; i++, pRgn++)
    {
        if (WinIntersectRect(hab, &intern, pRect, &pRgn->rect) == FALSE) {
            continue;
        } else {
            WinInvalidateRect(pRgn->hwnd, NULL, FALSE);
        }
    }
    return 0;
}

/*
 * shapeWMCreate:
 *      this initializes the "shape window".
 *      This gets called upon receiving WM_CREATE in fnwpShapeWindow.
 *      The procedure is as follows:
 *      1)  the bitmap in pData->hpsDraw is analyzed pixel by
 *          pixel to create a number of "regions" from it
 *          (which are not GPI regions, but an array of SHPREGION
 *          structures. At least one of these structures is
 *          created for each line. This is done by calling
 *          shapeBitmap2Regions.
 *      2)  We then call shapeRegions2Windows to actually create
 *          PM windows from all these structures.
 */

static PSHPWINCTLDATA shapeWMCreate(HWND hwnd, // in: shape window
                         PCREATESTRUCT pWin,    // in: create struct of WM_CREATE
                         PSHPCTLDATA pData)       // in: SHPCTLDATA struct (WM_CREATE mp1)
{
    PSHPWINCTLDATA   pCtrl;

    if (pData->hpsDraw == NULLHANDLE || pData->hpsMask == NULLHANDLE) {
        return NULL;
    }

    // create new PSHPWINCTLDATA structure
    if ((pCtrl = (PSHPWINCTLDATA) malloc(sizeof(SHPWINCTLDATA))) == NULL) {
        return NULL;
    }

    /*
     * Setup Common Window Parameters
     */

    pCtrl->hwndShape  = hwnd;
    pCtrl->hwndParent = pWin->hwndParent;
    pCtrl->hwndOwner  = pWin->hwndOwner ;
    pCtrl->id         = pWin->id;
    pCtrl->x          = pWin->x ;
    pCtrl->y          = pWin->y ;
    pCtrl->cx         = pWin->cx;
    pCtrl->cy         = pWin->cy;

    /*
     * Setup Image Window's Control Data
     */

    pCtrl->cx = pData->cx;
    pCtrl->cy = pData->cy;
    pCtrl->hpsDraw = pData->hpsDraw;

    // now create "regions" from bitmap;
    // this will store the "regions" in pCtrl
    shapeBitmap2Regions(pCtrl, pData->hpsMask);

    // now create as many rectangular PM
    // windows as we have "regions" in pCtrl
    shapeRegions2Windows(pCtrl);

    return pCtrl;
}

/*
 * shapeWMDestroy:
 *      this cleans up the shape's resources.
 *      Gets called upon receiving WM_DESTROY in
 *      fnwpShapeWindow.
 */

static void shapeWMDestroy(PSHPWINCTLDATA pCtrl)
{
    if (pCtrl == NULL) {
        return;
    }
    shapeFreeRegion(pCtrl);
    free(pCtrl);
}

/*
 *@@ fnwpShapeWindow:
 *      this is the window procedure for the "shape window".
 *      Register this procedure with WinRegisterWindowClass.
 *      This does the transformation of the bitmap into many
 *      rectangular subwindows upon WM_CREATE, each of which
 *      will use fnwpRegionSubwindow as the window proc.
 *      This window procedure forwards the following messages
 *      to its owner:
 *      --  WM_MOUSEMOVE
 *      --  WM_BUTTON1xxx
 *      --  WM_BUTTON2xxx
 *      --  WM_BUTTON3xxx
 *      --  WM_CHAR
 *      --  WM_VIOCHAR
 *      --  WM_BEGINDRAG
 *      --  WM_ENDDRAG
 *      --  WM_SINGLESELECT
 *      --  WM_OPEN
 *      --  WM_CONTEXTMENU
 *      --  WM_CONTEXTHELP
 *      --  WM_TEXTEDIT
 *      --  WM_BEGINSELECT
 *      --  WM_ENDSELECT
 */

MRESULT EXPENTRY fnwpShapeWindow(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    PSHPWINCTLDATA   pCtrl;
    HPS         hps  ;
    PSWP        pswp ;
    SHORT       sx, sy;

    pCtrl = (PSHPWINCTLDATA) WinQueryWindowPtr(hwnd, 0);

    switch (msg)
    {

        /*
         * WM_CREATE:
         *      shape window is being created.
         *      Parameters:
         *          PSHPCTLDATA mp1
         *          PCREATESTRUCT mp2
         *      Shaped windows must have a SHPCTLDATA
         *      structure in mp1.
         */

        case WM_CREATE:
            // TRACE("WM_CREATE\n");
            pCtrl = shapeWMCreate(hwnd,
                (PCREATESTRUCT)PVOIDFROMMP(mp2),
                (PSHPCTLDATA)PVOIDFROMMP(mp1));

            if (pCtrl == NULL)
                return (MRESULT) TRUE;

            // store control data in window words
            WinSetWindowPtr(hwnd, 0, (PVOID) pCtrl);
            return (MRESULT) FALSE;

        /*
         * WM_DESTROY:
         *      clean up.
         */

        case WM_DESTROY:
            // TRACE("WM_DESTORY\n");
            shapeWMDestroy(pCtrl);
            return (MRESULT) 0;

        /*
         * WM_ADJUSTWINDOWPOS:
         *      this needs to be manipulated to
         *      adjust the positions of all the
         *      subwindows instead.
         */

        case WM_ADJUSTWINDOWPOS:
            // TRACE("WM_ADJUSTWINDOWPOS\n");
            pswp = (PSWP) PVOIDFROMMP(mp1);
            // DUMPSWP(pswp);

            // enforce the size which we were given
            // in the beginning, because we cannot
            // change the size later
            pswp->cx = pCtrl->cx;
            pswp->cy = pCtrl->cy;

            // adjust the sub-windows
            shapeWMAdjustWindowPos(pCtrl, pswp);

            // never show ourselves
            pswp->fl &= ~SWP_SHOW;
            return (MRESULT) 0;

        /*
         * SHAPEWIN_MSG_UPDATE:
         *
         */

        case SHAPEWIN_MSG_UPDATE:
            shapeUpdateRegions(pCtrl, (PRECTL) PVOIDFROMMP(mp1));
            return (MRESULT) 0;

        /*
         * WM_QUERYDLGCODE:
         *
         */

        case WM_QUERYDLGCODE:
            // TRACE("WM_QUERYDLGCODE\n");
            return (MRESULT) DLGC_STATIC;

        /*
         * WM_PAINT:
         *      we just swallow this message because
         *      there's no "shape window" to be painted;
         *      instead, all the region windows get
         *      their own WM_PAINT message
         */

        case WM_PAINT:
            // TRACE("WM_PAINT\n");
            hps = WinBeginPaint(hwnd, NULLHANDLE, NULL);
            WinEndPaint(hps);
            return (MRESULT) 0;

        /*
         * all input-related messages:
         *      forward these to the owner
         */

        case WM_MOUSEMOVE    :
        case WM_BUTTON1DOWN  :
        case WM_BUTTON1UP    :
        case WM_BUTTON1CLICK :
        case WM_BUTTON1DBLCLK:
        case WM_BUTTON2DOWN  :
        case WM_BUTTON2UP    :
        case WM_BUTTON2CLICK :
        case WM_BUTTON2DBLCLK:
        case WM_BUTTON3DOWN  :
        case WM_BUTTON3UP    :
        case WM_BUTTON3CLICK :
        case WM_BUTTON3DBLCLK:
        case WM_CHAR         :
        case WM_VIOCHAR      :
        case WM_BEGINDRAG    :
        case WM_ENDDRAG      :
        case WM_SINGLESELECT :
        case WM_OPEN         :
        case WM_CONTEXTMENU  :
        case WM_CONTEXTHELP  :
        case WM_TEXTEDIT     :
        case WM_BEGINSELECT  :
        case WM_ENDSELECT    :
            return WinSendMsg(pCtrl->hwndOwner, msg, mp1, mp2);

    } // end switch (msg)
    return WinDefWindowProc(hwnd, msg, mp1, mp2);
}

/* ******************************************************************
 *                                                                  *
 *   Part 2: Shape frame functions                                  *
 *                                                                  *
 ********************************************************************/

static MRESULT EXPENTRY fnwpShapeFrame(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);

/*
 *@@ shpLoadBitmap:
 *      this creates a memory device context/presentation space
 *      and loads and selects a bitmap into it.
 *
 *      If pszBitmap == NULL, the bitmap is loaded from the
 *      specified resources, otherwise we attempt to load the
 *      bitmap file specified in pszBitmap (using gpihLoadBitmapFile).
 *
 *      Output in the given SHAPEFRAME structure:
 *      --  hdc                 device context handle
 *      --  hps                 presentation space handle
 *      --  hbm                 bitmap handle
 *      --  bmi                 BITMAPINFOHEADER of hbmp
 *
 *      All other fields are neither read nor written to.
 *      Returns TRUE if everything went OK.
 *      You can call this function directly before calling
 *      shpCreateWindows.
 *      Pass the SHAPEFRAME structure to shpFreeBitmap to clean up.
 */

BOOL shpLoadBitmap(HAB hab, // in: anchor block
                   PSZ pszBitmapFile, // in: OS/2 1.3 bmp file or NULL
                   HMODULE hmodResource, // in: module handle from where the
                            // resources should be loaded if
                            // pszBitmapFile == NULL. This can be 0 for
                            // the current EXE
                   ULONG idResource, // in: bitmap resource ID in that module
                   PSHAPEFRAME psb) // out: bitmap info
{
    SIZEL       siz;
    BOOL        brc;

    psb->hdc = DevOpenDC(hab, OD_MEMORY, "*", 0, NULL, NULLHANDLE);
    if (psb->hdc)
    {
        siz.cx = siz.cy = 0;
        psb->hps = GpiCreatePS(hab, psb->hdc, &siz,
                PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC);

        if (psb->hps)
        {
            // hbm = bitmapLoadFile(hab, *phps, pszBitmap);

            if (pszBitmapFile)
            {
                // load bitmap file
                ULONG ulError;
                psb->hbm = gpihLoadBitmapFile(psb->hps,
                                              pszBitmapFile,
                                              &ulError);
            } else
                // load resource bitmap
                psb->hbm = GpiLoadBitmap(psb->hps,
                                             hmodResource,
                                             idResource,
                                             0, 0);
            if (psb->hbm)
            {
                // store bitmap info in structure
                psb->bmi.cbFix = sizeof(psb->bmi);
                GpiQueryBitmapInfoHeader(psb->hbm, &psb->bmi);

                GpiSetBitmap(psb->hps, psb->hbm);
                brc = TRUE;
            }
        }
    }
    return (brc);
}

/*
 *@@ shpFreeBitmap:
 *      this cleans up resources allocated by shpLoadBitmap:
 *      delete the bitmap, destroy the HPS and HDC.
 */

VOID shpFreeBitmap(PSHAPEFRAME psb)
{
    if (psb->hbm != NULLHANDLE) {
        GpiDeleteBitmap(psb->hbm);
        psb->hbm = NULLHANDLE;
    }
    if (psb->hps != NULLHANDLE) {
        GpiDestroyPS(psb->hps);
        psb->hps = NULLHANDLE;
    }
    if (psb->hdc != NULLHANDLE) {
        DevCloseDC(psb->hdc);
        psb->hdc = NULLHANDLE;
    }
}

/*
 *@@ shpCreateWindows:
 *      this is a one-shot function for creating a
 *      shaped window from a bitmap. Before calling
 *      this function, you should call shpLoadBitmap
 *      to have the neccessary fields initialized.
 *      Otherwise you must initialize those fields for
 *      yourself.
 *
 *      Required fields in SHAPEFRAME for input:
 *      --  hps                 presentation space handle
 *      --  hbmp                bitmap handle
 *      --  bmi                 BITMAPINFOHEADER of hbmp
 *
 *      Output in that structure:
 *      --  hwndShapeFrame      invisible rectangular frame
 *                              window for the shape window
 *      --  hwndShape           visible shape window
 *      --  pfnFrame            original window procedure of
 *                              WC_FRAME (hwndShapeFrame),
 *                              which is subclassed here
 *                              using fnwpShapeFrame
 *      --  shpctrl             control data for hwndShape
 *
 *      Returns TRUE if everything went OK.
 *
 *      Note that the windows are only created; they are
 *      neither positioned nor shown on the screen. If you
 *      use WinSetWindowPos on the shape, you must always
 *      issue that twice: once for hwndShapeFrame without
 *      SWP_SHOW set, once for hwndShape with SWP_SHOW set.
 */

BOOL shpCreateWindows(HAB hab, PSHAPEFRAME psb)
{
    BOOL        brc = FALSE;

    if (psb->hbm)
    {
        // bitmap seems to be valid:
        FRAMECDATA  fcd;
        UCHAR       title[256];

        memset(&fcd, 0, sizeof(fcd));
        fcd.cb = sizeof(fcd);

        sprintf(title, "XFolderLogoShape"); // , ProgramName, pszBitmap);

        // create invisible frame
        psb->hwndShapeFrame = WinCreateWindow(
                HWND_DESKTOP,           // Parent window handle
                WC_FRAME,               // Frame Window Class
                title,                  // as Title
                0,                      // Window Style
                0, 0, 0, 0,             // Position & size
                NULLHANDLE,             // Owner Window
                HWND_TOP,               // Z-Order
                0,                      // Window ID
                &fcd,                   // Control Data
                NULL);                  // Presentation Parameter

        if (psb->hwndShapeFrame)
        {
            // store the SHAPEFRAME structure in the frame's window words
            // so that the subclassed frame can access the data
            WinSetWindowULong(psb->hwndShapeFrame, QWL_USER, (ULONG)psb);

            // subclass the frame window; store the original wnd proc in
            // the SHAPEFRAME structure
            psb->pfnFrame = WinSubclassWindow(psb->hwndShapeFrame, fnwpShapeFrame);

            if ((psb->hwndShapeFrame) && (psb->hps))
            {
                // OK, no errors so far

                // register shape window class; if this is already
                // registered, this won't hurt
                WinRegisterClass(hab, SHAPE_WINDOW_CLASS, fnwpShapeWindow, 0L, sizeof(PVOID));

                // create shape (image) window
                psb->shpctrl.cx = psb->bmi.cx;
                psb->shpctrl.cy = psb->bmi.cy;
                psb->shpctrl.hpsDraw = psb->hps;
                psb->shpctrl.hpsMask = psb->hps;

                // create the "shape" window
                psb->hwndShape = WinCreateWindow(
                        HWND_DESKTOP,           // Parent Window
                        SHAPE_WINDOW_CLASS,     // Window Class
                        NULL,                   // Window Text
                        0,                      // Window Style
                        0, 0, 0, 0,             // Pos & Size
                        psb->hwndShapeFrame,    // Owner Window
                        HWND_TOP,               // Z-Order
                        0,                      // Window ID
                        &psb->shpctrl,          // Control Data
                        NULL);                  // Pres. Param.

                if (psb->hwndShape)
                    // no error: only then return TRUE
                    brc = TRUE;
            }
        }
    }

    return (brc);
}

/*
 *@@ fnwpShapeFrame:
 *      this is the window proc for subclassing the shape frame window
 *      (shpCreateWindows above).
 *      The shaped window proc (fnwpShapeWindow) keeps forwarding messages
 *      to its owner (which is the frame here), so we better handle
 *      those messages.
 */

MRESULT EXPENTRY fnwpShapeFrame(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc;

    // get the SHAPEFRAME structure from the window words,
    // which has been stored there by shpCreateWindows
    PSHAPEFRAME psb = (PSHAPEFRAME)WinQueryWindowULong(hwnd, QWL_USER);

    // _Pmpf(("frame %08x %08x %08x", msg, mp1, mp2));

    switch (msg) {

        /*
         * WM_ADJUSTWINDOWPOS:
         *      forward the data we get here to the shape
         *      window (which will in turn reposition all
         *      the region windows);
         *      afterwards, always delete SHOW and set HIDE
         *      for ourselves
         */

        case WM_ADJUSTWINDOWPOS: {
            PSWP pswp = (PSWP) PVOIDFROMMP(mp1);
            POINTL ptl;
            WinQueryPointerPos(HWND_DESKTOP, &ptl);
            WinSetWindowPos(psb->hwndShape,
                            pswp->hwndInsertBehind,
                            // use current mouse position instead
                            // of the original ones
                            ptl.x,
                            ptl.y,
                            // pswp->x,
                            // pswp->y,
                            pswp->cx,
                            pswp->cy,
                            pswp->fl);
            pswp->fl &= ~SWP_SHOW;
            pswp->fl |=  SWP_HIDE;
            mrc = (*psb->pfnFrame)(hwnd, msg, mp1, mp2);
        break; }

        /* the shape window forwards these messages to us:
            WM_MOUSEMOVE
            WM_BUTTON1DOWN
            WM_BUTTON1UP
            WM_BUTTON1CLICK
            WM_BUTTON1DBLCLK
            WM_BUTTON2DOWN
            WM_BUTTON2UP
            WM_BUTTON2CLICK
            WM_BUTTON2DBLCLK
            WM_BUTTON3DOWN
            WM_BUTTON3UP
            WM_BUTTON3CLICK
            WM_BUTTON3DBLCLK
            WM_CHAR
            WM_VIOCHAR
            WM_BEGINDRAG
            WM_ENDDRAG
            WM_SINGLESELECT
            WM_OPEN
            WM_CONTEXTMENU
            WM_CONTEXTHELP
            WM_TEXTEDIT
            WM_BEGINSELECT
            WM_ENDSELECT */

        /*
         * WM_SINGLESELECT:
         *      if we are being clicked upon, bring the
         *      shape window to top instead
         */

        case WM_SINGLESELECT:
            WinSetWindowPos(psb->hwndShape, HWND_TOP, 0, 0, 0, 0, SWP_ZORDER);
            mrc = 0;
        break;

        /*
         * WM_BEGINDRAG:
         *      if we are being dragged with MB2, forward
         *      this to the shape window
         */

        case WM_BEGINDRAG:
            WinSendMsg(psb->hwndShapeFrame, WM_TRACKFRAME,
                MPFROMSHORT(TF_MOVE /* | TF_SETPOINTERPOS*/ ), NULL);
            mrc = 0;
        break;

        default:
            mrc = (*psb->pfnFrame)(hwnd, msg, mp1, mp2);
    }

    return (mrc);
}

