/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (C) 1992 - 1996 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *   rlmeter.c: Audio recording level window
 *
 *   Vidcap32 Source code
 *
 ***************************************************************************/

/*
 * This window class acts as a 'VU Meter' showing the current and peak
 * volume. Set the volume via the WMRL_SETLEVEL message (lParam is new level).
 * The peak level will be tracked by the control by means of a 2-second timer.
 */

#include <windows.h>
#include <windowsx.h>

#include "rlmeter.h"

#ifdef _WIN32
#ifndef EXPORT
#define EXPORT
#endif
#endif

LONG FAR PASCAL EXPORT
RLMeterProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

/*
 * generic window class to support a volume level display.
 *
 * The window has a white background, and draws a black filled
 * rectangle to show the current volume level, and a red line at the
 * peak. Every two seconds on a timer we lower the peak (we set the
 * saved peak value to 0 so that at the next update we move the line to
 * whatever is the current level.
 *
 * We store the pen and brush handles and the current and maximum levels
 * as window words using SetWindowWord on win16 and SetWindowLong on win32.
 */

// window data layout
#define WD_MAX      0                           // current max
#define WD_PREVMAX  (WD_MAX + sizeof(UINT))     // currently drawn max
#define WD_PREVLVL  (WD_PREVMAX + sizeof(UINT)) // currently drawn level

#define WD_PEN      (WD_PREVLVL + sizeof(UINT)) // pen for max line

#define WDBYTES     (WD_PEN + sizeof(UINT))     // window bytes to alloc

#ifdef _WIN32
#define SetWindowUINT     SetWindowLong
#define GetWindowUINT     GetWindowLong
#else
#define SetWindowUINT     SetWindowWord
#define GetWindowUINT     GetWindowWord
#endif


// call (if first instance) to register class
BOOL
RLMeter_Register(HINSTANCE hInstance)
{
    WNDCLASS cls;

    cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
    cls.hIcon          = NULL;
    cls.lpszMenuName   = NULL;
    cls.lpszClassName  = RLMETERCLASS;
    cls.hbrBackground  = GetStockObject(WHITE_BRUSH);
    cls.hInstance      = hInstance;
    cls.style          = CS_HREDRAW | CS_VREDRAW;
    cls.lpfnWndProc    = RLMeterProc;
    cls.cbClsExtra     = 0;
    cls.cbWndExtra     = WDBYTES;

    return RegisterClass(&cls);


}


LONG FAR PASCAL EXPORT
RLMeterProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message) {
    case WM_CREATE:
        // init current level and max to 0
        SetWindowUINT(hwnd, WD_MAX, 0);
        SetWindowUINT(hwnd, WD_PREVMAX, 0);
        SetWindowUINT(hwnd, WD_PREVLVL, 0);

        // create a red pen for the max line and store this
        SetWindowUINT(hwnd, WD_PEN,
                (UINT) CreatePen(PS_SOLID, 2, RGB(255, 0, 0)));

        break;

    case WM_DESTROY:
        // destroy the pen we created
        {
            HPEN hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
            if (hpen) {
                DeleteObject(hpen);
                SetWindowUINT(hwnd, WD_PEN, 0);
            }

            // also kill the timer we created
            KillTimer(hwnd, 0);
        }
        break;

    case WM_PAINT:
        /*
         * paint the entire control
         *
         * nb we must paint exactly as it is currently drawn because we
         * may be clipped to only part of the control. Thus we must draw
         * the max at WD_PREVMAX as it is currently drawn, since WD_MAX
         * may have been set to 0 and not yet drawn - in this case, with
         * some unfortunate timing and clipping, we would have two max lines.
         */
        {
            PAINTSTRUCT ps;
            HDC hdc;
            RECT rc, rcFill;
            HPEN hpenOld, hpen;

            hdc = BeginPaint(hwnd, &ps);

            GetClientRect(hwnd, &rc);

            // treat the level as a percentage and fill that much of the
            // control with black (from left)
            rcFill = rc;
            rcFill.right = (rc.right * GetWindowUINT(hwnd, WD_PREVLVL)) / 100;
            SetBkColor(hdc, RGB(0,0,0));
            // easy way to fill without creating a brush
            ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);

            // draw the max line
            rcFill.right = (rc.right * GetWindowUINT(hwnd, WD_PREVLVL)) / 100;
            hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
            hpenOld = SelectObject(hdc, hpen);
            MoveToEx(hdc, rcFill.right, rcFill.top, NULL);
            LineTo(hdc, rcFill.right, rcFill.bottom);
            SelectObject(hdc, hpenOld);

            EndPaint(hwnd, &ps);

        }
        break;

    case WMRL_SETLEVEL:
        // set new level, and update the displayed level block and max line
        {
            RECT rc, rcFill;
            UINT uMax, uPrevMax, uPrevLevel, uLevel;
            HDC hdc;

            // new level is lParam
            uLevel = (UINT) lParam;

            // fetch other parameters
            uMax = GetWindowUINT(hwnd, WD_MAX);
            uPrevMax = GetWindowUINT(hwnd, WD_PREVMAX);
            uPrevLevel = GetWindowUINT(hwnd, WD_PREVLVL);


            // decay the max level. This rate works best if we are called
            // to update every 1/20th sec - in this case the decay will be
            // 64% in a second.
            if (uMax > 0) {
                uMax = (uMax * 2007) / 2048;     // = 0.98 * uMax
            }

            hdc = GetDC(hwnd);

            GetClientRect(hwnd, &rc);
            rcFill = rc;

            // is the current level a new peak ?
            if (uLevel > uMax) {
                uMax = uLevel;
            }

            SetWindowUINT(hwnd, WD_MAX, uMax);

            // if the max has moved, erase the old line
            if (uMax != uPrevMax) {
                // white out the line by filling a 2-pixel wide rect
                rcFill.right = ((rc.right * uPrevMax) / 100) + 1;
                rcFill.left = rcFill.right - 2;
                SetBkColor(hdc, RGB(255, 255, 255));
                ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);
            }

            // calculate the area to update
            rcFill.right = (rc.right * uPrevLevel) / 100;
            rcFill.left = (rc.right * uLevel) / 100;

            // are we erasing (lowering level) or drawing more black?
            if (rcFill.right > rcFill.left) {

                // level has dropped - so fill with white down to new level
                SetBkColor(hdc, RGB(255, 255, 255));
            } else {
                // level has gone up so fill with black up to new level
                int t;

                t = rcFill.right;
                rcFill.right = rcFill.left;
                rcFill.left = t;

                SetBkColor(hdc, RGB(0, 0, 0));

                // fill a little extra to ensure no rounding gaps
                if (rcFill.left > 0) {
                    rcFill.left -= 1;
                }
            }
            ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rcFill, NULL, 0, NULL);
            SetWindowUINT(hwnd, WD_PREVLVL, uLevel);

            // draw the new max line if needed
            if (uMax != uPrevMax) {
                HPEN hpen, hpenOld;

                rcFill.right = (rc.right * uMax) /100;

                hpen = (HPEN) GetWindowUINT(hwnd, WD_PEN);
                hpenOld = SelectObject(hdc, hpen);
                MoveToEx(hdc, rcFill.right, rcFill.top, NULL);
                LineTo(hdc, rcFill.right, rcFill.bottom);
                SelectObject(hdc, hpenOld);

                SetWindowUINT(hwnd, WD_PREVMAX, uMax);
            }
            ReleaseDC(hwnd, hdc);
            return(0);
        }

    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}



