
/*
 *@@sourcefile animate.c:
 *      contains helper functions to animate a static icon control.
 *      These functions do not rely on the XFolder code, but could
 *      be used in any program.
 *
 *      This is a new file with V0.81. Most of this code used to reside
 *      in common.c with previous versions.
 *
 *      Function prefixes (new with V0.81):
 *      --  anm*   Animation helper functions
 *
 *      To animate a static icon control, you can simply create it
 *      with the dialog editor. Then do the following in your code:
 *
 *      1) load the dlg box template (using WinLoadDlg);
 *
 *      2) load all the icons for the animation in an array of
 *         HPOINTERs (check xsdLoadAnimation in xshutdwn.c for an
 *         example);
 *
 *      3) call anmPrepareAnimation, e.g.:
 +             anmPrepareAnimation(WinWindowFromID(hwndDlg, ID_ICON), // get icon hwnd
 +                             8,                   // no. of icons for the anim
 +                             &ahptr[0],           // ptr to first icon in the array
 +                             150,                 // delay
 +                             TRUE);               // start animation now
 *
 *      4) call WinProcessDlg(hwndDlg);
 *
 *      5) stop the animation, e.g.:
 +              anmStopAnimation(WinWindowFromID(hwndDlg, ID_ICON));
 *
 *      6) destroy the dlg window;
 *
 *      7) free all the HPOINTERS loaded above (check xsdFreeAnimation in
 *         xshutdwn.c for an example).
 *
 *@@include #define INCL_WINWINDOWMGR
 *@@include #define INCL_WINPOINTERS
 *@@include #include <os2.h>
 *@@include #include "animate.h"
 */

/*
 *      Copyright (C) 1997-99 Ulrich M”ller.
 *      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_DOSDEVICES
#define INCL_DOSDEVIOCTL

#define INCL_WIN
#define INCL_WINSYS

#define INCL_GPILOGCOLORTABLE
#define INCL_GPIPRIMITIVES

#include <os2.h>

#include "animate.h"

// #define _PMPRINTF_
#include "pmprintf.h"

#include "dosh.h"
#include "winh.h"
#include "gpih.h"

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

/*
 *@@ fnwpAnimatedIcon:
 *      subclassed wnd proc for static icons subclassed
 *      with anmPrepareStaticIcon (below). This func
 *      intercepts a few messages to improve icon painting
 *      and also handles the timer for animations, if this
 *      has been enabled.
 */

MRESULT EXPENTRY fnwpAnimatedIcon(HWND hwndStatic, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    PANIMATIONDATA pa = (PANIMATIONDATA)WinQueryWindowULong(hwndStatic, QWL_USER);
                // animation data which was stored in window words
    PFNWP   OldStaticProc = NULL;
    MRESULT mrc = NULL;

    if (pa) {
        OldStaticProc = pa->OldStaticProc;

        switch(msg) {

            /*
             * WM_TIMER:
             *      this timer is started by the anm* funcs
             *      below. Proceed to the next animation step.
             */

            case WM_TIMER: {
                pa->usAniCurrent++;
                if (pa->usAniCurrent >= pa->usAniCount)
                    pa->usAniCurrent = 0;

                WinSendMsg(hwndStatic,
                        SM_SETHANDLE,
                        (MPARAM)pa->ahptrAniIcons[pa->usAniCurrent],
                        (MPARAM)NULL);
            break; }

            /*
             * SM_SETHANDLE:
             *      this is the normal message sent to a static
             *      icon control to change its icon. Unfortunately,
             *      the standard static control paints garbage if
             *      the icons contain transparent areas, and the
             *      display often flickers too.
             *      We improve this by creating one bitmap from
             *      the icon that we were given which we can then
             *      simply copy to the screen in one step in
             *      WM_PAINT.
             */

            case SM_SETHANDLE: {
                HDC hdcMem;
                PSZ pszData[4] = { "Display", NULL, NULL, NULL };
                HPS hpsMem;
                SIZEL sizlPage = {0, 0};
                BITMAPINFOHEADER2 bmp;
                PBITMAPINFO2 pbmi = NULL;
                // HBITMAP hbm = NULLHANDLE;
                POINTL aptl[3];
                LONG alData[2];

                LONG lBkgndColor = winhQueryPresColor(
                                WinQueryWindow(hwndStatic, QW_PARENT),
                                PP_BACKGROUNDCOLOR,
                                "DialogBackground",
                                "255 255 255");

                HAB hab = WinQueryAnchorBlock(hwndStatic);

                HPS hps = WinGetPS(hwndStatic);

                // store new icon in our own structure
                pa->hptr = (HPOINTER)mp1;

                // switch to RGB mode
                GpiCreateLogColorTable(hps, 0,
                    LCOLF_RGB,
                    0, 0, NULL);

                // if we have created a bitmap previously,
                // delete it
                if (pa->hbm)
                    GpiDeleteBitmap(pa->hbm);

                // create a memory PS
                if (gpihCreateMemPS(hab, &hdcMem, &hpsMem))
                {
                    // switch to RGB mode
                    GpiCreateLogColorTable(hpsMem, 0,
                        LCOLF_RGB,
                        0, 0, NULL);

                    // create a suitable bitmap w/ the size of the
                    // static control
                    if (pa->hbm = gpihCreateBitmap(hpsMem, &(pa->rclIcon)))
                    {
                        // associate the bit map with the memory PS
                        if (GpiSetBitmap(hpsMem, pa->hbm) != HBM_ERROR)
                        {
                            POINTL ptl = {0, 0};
                            POINTERINFO pi;

                            // fill the bitmap with the current static
                            // background color
                            GpiMove(hpsMem, &ptl);
                            ptl.x = pa->rclIcon.xRight;
                            ptl.y = pa->rclIcon.yTop;
                            GpiSetColor(hpsMem,
                                    lBkgndColor);
                            GpiBox(hpsMem,
                                    DRO_FILL, // interior only
                                    &ptl,
                                    0, 0);    // no corner rounding

                            // now get the bitmaps for the new icon
                            // that we were given in mp1.
                            // Each icon consists of two (really three)
                            // bitmaps, which are stored in the POINTERINFO
                            // structure:
                            //   pi.hbmColor    is the actual bitmap to be
                            //                  drawn. The parts that are
                            //                  to be transparent or inverted
                            //                  are black in this image.
                            //   pi.hbmPointer  has twice the height of
                            //                  hbmColor. The upper bitmap
                            //                  contains an XOR mask (for
                            //                  inverting parts), the lower
                            //                  bitmap an AND mask (for
                            //                  transparent parts).
                            if (WinQueryPointerInfo(pa->hptr, &pi))
                            {
                                POINTL  aptl[4];

                                memset(aptl, 0, sizeof(POINTL) * 4);

                                aptl[1].x = pa->ulSysIconSize;
                                aptl[1].y = pa->ulSysIconSize;

                                aptl[3].x = pa->ulSysIconSize + 1;
                                aptl[3].y = pa->ulSysIconSize + 1;

                                GpiSetColor(hpsMem, CLR_WHITE);
                                GpiSetBackColor(hpsMem, CLR_BLACK);

                                // GpiErase(hpsMem);

                                // work on the AND image
                                GpiWCBitBlt(hpsMem,
                                        pi.hbmPointer,
                                        4L,         // point count, always 4
                                        &aptl[0],   // point array
                                        ROP_SRCAND,
                                        BBO_OR);

                                // paint the real image
                                if (pi.hbmColor)
                                    GpiWCBitBlt(hpsMem,
                                            pi.hbmColor,
                                            4L,
                                            &aptl[0],   // point array
                                            ROP_SRCPAINT,
                                            BBO_OR);

                                GpiSetColor(hpsMem, lBkgndColor);
                                // work on the XOR image
                                aptl[2].y = pa->ulSysIconSize;
                                aptl[3].y = (pa->ulSysIconSize * 2) + 1;
                                GpiWCBitBlt(hpsMem,
                                        pi.hbmPointer,
                                        4L,         // point count, always 4
                                        &aptl[0],   // point array
                                        ROP_SRCINVERT,
                                        BBO_OR);
                            }
                        }
                    }
                    GpiDestroyPS(hpsMem);
                    DevCloseDC(hdcMem);
                } // end if (hdcMem = DevOpenDC(

                // enforce WM_PAINT
                WinInvalidateRect(hwndStatic, NULL, FALSE);

                /* WinFillRect(hps, &(pa->rclIcon),
                    pa->lBkgndColor);

                if (pa->hptr) {
                    WinDrawPointer(hps, 0, 0, pa->hptr, DP_NORMAL);
                } */

                WinReleasePS(hps);
            break; }

            /*
             * WM_PAINT:
             *      "normal" paint; this only arrives here if the
             *      icon needs to be repainted for reasons other
             *      than changing the icon.
             */

            case WM_PAINT: {
                RECTL rcl;
                POINTL ptl = {0, 0};
                POINTERINFO pi;
                HPS hps = WinBeginPaint(hwndStatic, NULLHANDLE, &rcl);
                // draw the bitmap that've previously created
                WinDrawBitmap(hps,
                        pa->hbm,
                        NULL,     // whole bmp
                        &ptl,
                        0, 0,     // no colors
                        DBM_NORMAL);
                WinEndPaint(hps);
            break; }

            /*
             * WM_DESTROY:
             *      clean up.
             */

            case WM_DESTROY: {
                // undo subclassing in case more WM_TIMERs come in
                WinSubclassWindow(hwndStatic, OldStaticProc);

                if (pa->hbm)
                    GpiDeleteBitmap(pa->hbm);

                // clean up ANIMATIONDATA struct
                WinSetWindowULong(hwndStatic, QWL_USER, (ULONG)NULL);
                free(pa);

                mrc = OldStaticProc(hwndStatic, msg, mp1, mp2);
            break; }

            default:
                mrc = OldStaticProc(hwndStatic, msg, mp1, mp2);
       }
    }
    return (mrc);
}

/*
 *@@ anmPrepareStaticIcon:
 *      turns a static control into one which properly
 *      displays icons when given a SM_SETHANDLE msg.
 *      This is done by subclassing the static control
 *      with fnwpAnimatedIcon.
 *      This function gets called by anmPrepareAnimation,
 *      so you need not call it independently.
 *      This func does _not_ start an animation yet.
 *      Returns a PANIMATIONDATA if subclassing succeeded or
 *      NULL upon errors. If you only call this function, you
 *      do not need this structure; it is needed by
 *      anmPrepareAnimation though.
 *      The subclassed static icon func above automatically
 *      cleans up resources, so you don't have to worry about
 *      that either.
 */

PANIMATIONDATA anmPrepareStaticIcon(HWND hwndStatic,
                    USHORT usAnimCount) // needed for allocating extra memory;
                                        // this must be at least 1
{
    PANIMATIONDATA pa = NULL;
    PFNWP OldStaticProc = WinSubclassWindow(hwndStatic, fnwpAnimatedIcon);
    if (OldStaticProc) {
        pa = malloc(
                        sizeof(ANIMATIONDATA)
                      + ((usAnimCount-1) * sizeof(HPOINTER))
                   );
        pa->OldStaticProc = OldStaticProc;
        WinQueryWindowRect(hwndStatic, &(pa->rclIcon));
        pa->hbm = NULLHANDLE;
        pa->ulSysIconSize = WinQuerySysValue(HWND_DESKTOP, SV_CXICON);

        WinSetWindowULong(hwndStatic, QWL_USER, (ULONG)pa);
    }
    return (pa);
}

/*
 *@@ anmPrepareAnimation:
 *      this function makes a regular static icon
 *      control an animated one. This is the one-shot
 *      function for animating static icon controls.
 *      It calls anmPrepareStaticIcon first, then uses
 *      the passed parameters to prepare an animation:
 *      pahptr must point to an array of HPOINTERs
 *      which must contain usAnimCount icon handles.
 *      If (fStartAnimation == TRUE), the animation
 *      is started already. Otherwise you'll have
 *      to call anmStartAnimation yourself.
 *      When the icon control is destroyed, the
 *      subclassed window proc (fnwpAnimatedIcon)
 *      automatically cleans up resources. However,
 *      the icons in *pahptr are not freed.
 */

BOOL anmPrepareAnimation(HWND hwndStatic,   // icon hwnd
                USHORT usAnimCount,         // no. of anim steps
                HPOINTER *pahptr,           // array of HPOINTERs
                ULONG ulDelay,              // delay per anim step (in ms)
                BOOL fStartAnimation)       // TRUE: start animation now
{
    PANIMATIONDATA paNew = anmPrepareStaticIcon(hwndStatic, usAnimCount);
    if (paNew) {
        paNew->ulDelay = ulDelay;
        // paNew->OldStaticProc already set
        paNew->hptr = NULLHANDLE;
        // paNew->rclIcon already set
        paNew->usAniCurrent = 0;
        paNew->usAniCount = usAnimCount;
        memcpy(&(paNew->ahptrAniIcons), pahptr,
                        (usAnimCount * sizeof(HPOINTER)));

        if (fStartAnimation) {
            WinStartTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic,
                    1, ulDelay);
            WinPostMsg(hwndStatic, WM_TIMER, NULL, NULL);
        }
    }
    return (paNew != NULL);
}

/*
 *@@ anmStartAnimation:
 *      starts an animation that not currently running. You
 *      must prepare the animation by calling anmPrepareAnimation
 *      first.
 */

BOOL anmStartAnimation(HWND hwndStatic)
{
    BOOL brc = FALSE;
    PANIMATIONDATA pa = (PANIMATIONDATA)WinQueryWindowULong(hwndStatic, QWL_USER);
    if (pa)
        if (WinStartTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic,
                        1, pa->ulDelay))
        {
            brc = TRUE;
            WinPostMsg(hwndStatic, WM_TIMER, NULL, NULL);
        }
    return (brc);
}

/*
 *@@ anmStopAnimation:
 *      stops an animation that is currently running.
 *      This does not free any resources.
 */

BOOL anmStopAnimation(HWND hwndStatic)
{
    return (WinStopTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic, 1));
}

/*
 *@@ anmBlowUpBitmap:
 *      this displays an animation based on a given bitmap.
 *      The bitmap is "blown up" in that it is continually
 *      increased in size until the original size is reached.
 *      The animation is calculated so that it lasts exactly
 *      ulAnimation milliseconds, no matter how fast the
 *      system is.
 *      You should run this routine in a thread with higher-
 *      than-normal priority, because otherwise the kernel
 *      might interrupt the display.
 *      Returns the count of animation steps that were drawn.
 *      This is dependent on the speed of the system.
 */

BOOL anmBlowUpBitmap(HPS hps,               // in: from WinGetPS(HWND_DESKTOP)
                     HBITMAP hbm,           // in: bitmap to be displayed
                     ULONG ulAnimationTime) // in: total animation time (ms)
{
    ULONG   ulrc = 0;
    ULONG   ulInitialTime,
            ulNowTime;
            // ulCurrentSize = 10;

    if (hps) {
        POINTL  ptl = {0, 0};
        RECTL   rtlStretch;
        ULONG   ul,
                ulSteps = 20;
        BITMAPINFOHEADER bih;
        GpiQueryBitmapParameters(hbm, &bih);
        /* ptl.y = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN)
                        - BMPSPACING
                        - bih.cy; */
        ptl.x = (WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN)
                        - bih.cx) / 2;
        ptl.y = (WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN)
                        - bih.cy) / 2;

        // we now use ul for the current animation step,
        // which is a pointer on a scale from 1 to ulAnimationTime;
        // ul will be recalculated after each animation
        // step according to how much time the animation
        // has cost on this display so far. This has the
        // following advantages:
        // 1) no matter how fast the system is, the animation
        //    will always last ulAnimationTime milliseconds
        // 2) since large bitmaps take more time to calculate,
        //    the animation won't appear to slow down then
        ulInitialTime = doshGetULongTime();
        ul = 1;
        ulSteps = 1000;
        do {
            LONG cx = (((bih.cx-20) * ul) / ulSteps) + 20;
            LONG cy = (((bih.cy-20) * ul) / ulSteps) + 20;
            rtlStretch.xLeft   = ptl.x + ((bih.cx - cx) / 2);
            rtlStretch.yBottom = ptl.y + ((bih.cy - cy) / 2);
            rtlStretch.xRight = rtlStretch.xLeft + cx;
            rtlStretch.yTop   = rtlStretch.yBottom + cy;

            WinDrawBitmap(hps, hbm, NULL, (PPOINTL)&rtlStretch,
                    0, 0,       // we don't need colors
                    DBM_STRETCH);

            ulNowTime = doshGetULongTime();

            // recalculate ul: rule of three based on the
            // time we've spent on animation so far
            ul = (ulSteps
                    * (ulNowTime - ulInitialTime)) // time spent so far
                    / ulAnimationTime;      // time to spend altogether

            ulrc++;                         // return count

        } while (ul < ulSteps);

        // finally, draw the 1:1 version
        WinDrawBitmap(hps, hbm, NULL, &ptl,
                0, 0,       // we don't need colors
                DBM_NORMAL);

    } // end if (hps)

    return (ulrc);
}

#define LAST_WIDTH 2
#define LAST_STEPS 50
#define WAIT_TIME  10

/*
 *@@ anmPowerOff:
 *      displays an animation that looks like a
 *      monitor being turned off; hps must have
 *      been acquired using WinGetScreenPS,
 *      ulSteps should be around 40-50.
 */

VOID anmPowerOff(HPS hps, ULONG ulSteps)
{
    RECTL       rclScreen, rclNow, rclLast, rclDraw;
    ULONG       ul = ulSteps;
    ULONG       ulPhase = 1;

    WinQueryWindowRect(HWND_DESKTOP, &rclScreen);

    WinShowPointer(HWND_DESKTOP, FALSE);

    memcpy(&rclLast, &rclScreen, sizeof(RECTL));

    // In order to draw the animation, we tell apart three
    // "phases", signified by the ulPhase variable. While
    // ulPhase != 99, we stay in a do-while loop.
    ul = 0;
    ulPhase = 1;

    do {
        ULONG ulFromTime = doshGetULongTime();

        if (ulPhase == 1)
        {
            // Phase 1: "shrink" the screen by drawing black
            // rectangles from the edges towards the center
            // of the screen. With every loop, we draw the
            // rectangles a bit closer to the center, until
            // the center is black too. Sort of like this:

            //          ***************************
            //          *       black             *
            //          *                         *
            //          *      .............      *
            //          *      . rclNow:   .      *
            //          *  ->  . untouched .  <-  *
            //          *      .............      *
            //          *            ^            *
            //          *            !            *
            //          ***************************

            // rclNow contains the rectangle _around_ which
            // the black rectangles are to be drawn. With
            // every iteration, rclNow is reduced in size.
            rclNow.xLeft = ((rclScreen.yTop / 2) * ul / ulSteps );
            rclNow.xRight = (rclScreen.xRight) - rclNow.xLeft;
            rclNow.yBottom = ((rclScreen.yTop / 2) * ul / ulSteps );
            rclNow.yTop = (rclScreen.yTop) - rclNow.yBottom;

            if (rclNow.yBottom > (rclNow.yTop - LAST_WIDTH) ) {
                rclNow.yBottom = (rclScreen.yTop / 2) - LAST_WIDTH;
                rclNow.yTop = (rclScreen.yTop / 2) + LAST_WIDTH;
            }

            // draw black rectangle on top of rclNow
            rclDraw.xLeft = rclLast.xLeft;
            rclDraw.xRight = rclLast.xRight;
            rclDraw.yBottom = rclNow.yTop;
            rclDraw.yTop = rclLast.yTop;
            WinFillRect(hps, &rclDraw, CLR_BLACK);

            // draw black rectangle left of rclNow
            rclDraw.xLeft = rclLast.xLeft;
            rclDraw.xRight = rclNow.xLeft;
            rclDraw.yBottom = rclLast.yBottom;
            rclDraw.yTop = rclLast.yTop;
            WinFillRect(hps, &rclDraw, CLR_BLACK);

            // draw black rectangle right of rclNow
            rclDraw.xLeft = rclNow.xRight;
            rclDraw.xRight = rclLast.xRight;
            rclDraw.yBottom = rclLast.yBottom;
            rclDraw.yTop = rclLast.yTop;
            WinFillRect(hps, &rclDraw, CLR_BLACK);

            // draw black rectangle at the bottom of rclNow
            rclDraw.xLeft = rclLast.xLeft;
            rclDraw.xRight = rclLast.xRight;
            rclDraw.yBottom = rclLast.yBottom;
            rclDraw.yTop = rclNow.yBottom;
            WinFillRect(hps, &rclDraw, CLR_BLACK);

            // remember rclNow for next iteration
            memcpy(&rclLast, &rclNow, sizeof(RECTL));

            // done with "shrinking"?
            if ( rclNow.xRight < ((rclScreen.xRight / 2) + LAST_WIDTH) ) {
                ulPhase = 2; // exit
            }

        } else if (ulPhase == 2) {
            // Phase 2: draw a horizontal white line about
            // where the last rclNow was. This is only
            // executed once.

            //          ***************************
            //          *       black             *
            //          *                         *
            //          *                         *
            //          *        -----------      *
            //          *                         *
            //          *                         *
            //          *                         *
            //          ***************************

            rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
            rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
            rclDraw.yBottom = (rclScreen.yTop * 1 / 4);
            rclDraw.yTop = (rclScreen.yTop * 3 / 4);
            WinFillRect(hps, &rclDraw, CLR_WHITE);

            ulPhase = 3;
            ul = 0;

        } else if (ulPhase == 3) {
            // Phase 3: make the white line shorter with
            // every iteration by drawing black rectangles
            // above it. These are drawn closer to the
            // center with each iteration.

            //          ***************************
            //          *       black             *
            //          *                         *
            //          *                         *
            //          *       ->  ----  <-      *
            //          *                         *
            //          *                         *
            //          *                         *
            //          ***************************

            rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
            rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
            rclDraw.yTop = (rclScreen.yTop * 3 / 4);
            rclDraw.yBottom = (rclScreen.yTop * 3 / 4) - ((rclScreen.yTop * 1 / 4) * ul / LAST_STEPS);
            if (rclDraw.yBottom < ((rclScreen.yTop / 2) + LAST_WIDTH))
                rclDraw.yBottom = ((rclScreen.yTop / 2) + LAST_WIDTH);
            WinFillRect(hps, &rclDraw, CLR_BLACK);

            rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
            rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
            rclDraw.yBottom = (rclScreen.yTop * 1 / 4);
            rclDraw.yTop = (rclScreen.yTop * 1 / 4) + ((rclScreen.yTop * 1 / 4) * ul / LAST_STEPS);
            if (rclDraw.yTop > ((rclScreen.yTop / 2) - LAST_WIDTH))
                rclDraw.yBottom = ((rclScreen.yTop / 2) - LAST_WIDTH);

            WinFillRect(hps, &rclDraw, CLR_BLACK);

            ul++;
            if (ul > LAST_STEPS) {
                ulPhase = 99;
            }
        }

        ul++;

        while (doshGetULongTime() < ulFromTime + WAIT_TIME) {
            // PSZ p = NULL; // keep compiler happy
        }
    } while (ulPhase != 99);

    // sleep a while
    DosSleep(500);
    WinFillRect(hps, &rclScreen, CLR_BLACK);
    DosSleep(500);

    WinShowPointer(HWND_DESKTOP, TRUE);
}



