#define OEMRESOURCE     // setting this gets OBM_ constants in windows.h
#include <windows.h>
#include "ctls.h"
#pragma hdrstop

#define  MyModuleHandle    GetModuleHandle( NULL )
#define  MYASSERT(x)
#define  MyFree(x)         LocalFree( x )

PWSTR DuplicateString( PWSTR pszOriginal )
{
   PWSTR    pszCopy;

   pszCopy = LocalAlloc( LPTR, ( 1 + lstrlenW( pszOriginal ) ) * sizeof( *pszOriginal ) );

   if ( NULL != pszCopy )
   {
      lstrcpy( pszCopy, pszOriginal );
   }

   return pszCopy;
}

////////////////////////////////////////////
//
// Bitmap control
//
////////////////////////////////////////////

PCWSTR szBMPCLASS = L"_mybmp";


LRESULT
BmpClassWndProc(
    IN HWND   hwnd,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    );


BOOL
InitializeBmpClass(
    VOID
    )
{
    WNDCLASS wc;
    BOOL b;

    if(GetClassInfo(MyModuleHandle,szBMPCLASS,&wc)) {
        b = TRUE;
    } else {

        wc.lpszClassName = szBMPCLASS;
        wc.style         = CS_GLOBALCLASS;
        wc.lpfnWndProc   = BmpClassWndProc;
        wc.hInstance     = MyModuleHandle;
        wc.hIcon         = NULL;
        wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
        wc.hbrBackground = NULL;
        wc.lpszMenuName  = NULL;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;

        b = RegisterClass(&wc);
    }

    return(b);
}


VOID
DestroyBmpClass(
    VOID
    )
{
    WNDCLASS wc;

    if(GetClassInfo(MyModuleHandle,szBMPCLASS,&wc)) {
        //
        // Hope there are no more windows using the class!
        //
        MYASSERT(!FindWindow(szBMPCLASS,NULL));
        UnregisterClass(szBMPCLASS,MyModuleHandle);
    }
}


VOID
BmpClassPaint(
    IN HWND hwnd
    )
{
    PAINTSTRUCT ps;
    unsigned BmpId;
    HDC hdc, hdcMem;
    HBITMAP hbm,hbmOld;
    BITMAP bm;

    BmpId = GetDlgCtrlID(hwnd);

    hdc = BeginPaint(hwnd,&ps);
    if(hbm = LoadBitmap((HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),MAKEINTRESOURCE(BmpId))) {
        GetObject(hbm, sizeof(bm),&bm);
        if(hdcMem = CreateCompatibleDC(hdc)) {
            if(hbmOld = SelectObject(hdcMem,hbm)) {
                BitBlt(hdc,0,0,bm.bmWidth,bm.bmHeight,hdcMem,0,0,SRCCOPY);
                SelectObject(hdcMem,hbmOld);
            }
            DeleteDC(hdcMem);
        }
        DeleteObject(hbm);
    }
    EndPaint(hwnd,&ps);
}


LRESULT
BmpClassWndProc(
    IN HWND   hwnd,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    switch(msg) {

    case WM_NCCREATE:

        SetWindowLong(
            hwnd,
            GWL_STYLE,
            GetWindowLong(hwnd,GWL_STYLE) | WS_BORDER
            );

        return(TRUE);

    case WM_PAINT:

        BmpClassPaint(hwnd);
        return(0);
    }

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


////////////////////////////////////////////
//
// Action item list control
//
////////////////////////////////////////////

//
// Define locations in extra window storage
//
#define AIL_FONT        (0)
#define AIL_BOLDFONT    (sizeof(LONG))
#define AIL_BOLDITEM    (2*sizeof(LONG))
#define AIL_TEXT        (3*sizeof(LONG))
#define AIL_LINECOUNT   (4*sizeof(LONG))
#define AIL_FREEFONTS   (5*sizeof(LONG))

#define AIL_EXTRA       6

PCWSTR szActionItemListClassName = L"$$$ActionItemList";


VOID
ActionItemListPaint(
    IN HWND hwnd
    )
{

    PAINTSTRUCT PaintStruct;
    PWSTR p,Text;
    UINT LineCount;
    HFONT OldFont,Font,BoldFont;
    UINT HighlightedItem;
    UINT i;
    int Length;
    int y;
    int yDelta;
    HBITMAP Bitmap,OldBitmap;
    BITMAP bitmap;
    HDC MemoryDC;
    SIZE Size;
    RECT rect;
    int Spacing;
    #define BORDER 3

    if(!BeginPaint(hwnd,&PaintStruct)) {
        return;
    }

    //
    // If no text, nothing to do.
    //
    if(Text = (PWSTR)GetWindowLong(hwnd,AIL_TEXT)) {
        LineCount = (UINT)GetWindowLong(hwnd,AIL_LINECOUNT);
    }

    if(!Text || !LineCount) {
        return;
    }

    //
    // Get value indicating which item is to be bolded.
    //
    HighlightedItem = (UINT)GetWindowLong(hwnd,AIL_BOLDITEM);

    //
    // Get font handles.
    //
    Font = (HFONT)GetWindowLong(hwnd,AIL_FONT);
    BoldFont = (HFONT)GetWindowLong(hwnd,AIL_BOLDFONT);

    //
    // Select the non-boldface font to get the handle of
    // the currently selected font.
    //
    OldFont = SelectObject(PaintStruct.hdc,Font);

    //
    // Set text background color.
    //
    SetBkColor(PaintStruct.hdc,GetSysColor(COLOR_3DFACE));

    //
    // Load the little triangle bitmap and create a compatible DC for it.
    //
    Bitmap = LoadBitmap(NULL,MAKEINTRESOURCE(OBM_MNARROW));

    if(MemoryDC = CreateCompatibleDC(PaintStruct.hdc)) {

        OldBitmap = SelectObject(MemoryDC,Bitmap);
        GetObject(Bitmap,sizeof(BITMAP),&bitmap);
    }

    Spacing = GetSystemMetrics(SM_CXICON) / 2;

    //
    // Treat the text as a series of lines and draw each one.
    //
    p = Text;
    y = 0;
    for(i=0; i<LineCount; i++) {

        //
        // Calculate the line's height based on the boldface font.
        // This is used to get to the y coord of the next line.
        //
        SelectObject(PaintStruct.hdc,BoldFont);

        GetClientRect(hwnd,&rect);
        rect.left = (2 * BORDER) + Spacing;
        rect.bottom = 0;

        DrawText(PaintStruct.hdc,p,lstrlen(p),&rect,DT_CALCRECT|DT_WORDBREAK);

        yDelta = rect.bottom + (2*BORDER);

        //
        // Change font to non-boldface for this line if necessary.
        //
        if(i != HighlightedItem) {
            SelectObject(PaintStruct.hdc,Font);
        }

        rect.top = y + BORDER;
        rect.left = (2 * BORDER) + Spacing;
        rect.bottom = rect.top + yDelta;

        //
        // Draw the line's text.
        //
        Length = lstrlen(p);
        DrawText(PaintStruct.hdc,p,Length,&rect,DT_WORDBREAK);

        //
        // Draw the little triangle thing if necessary.
        //
        if((i == HighlightedItem) && Bitmap && MemoryDC) {

            GetTextExtentPoint(PaintStruct.hdc,L"WWWWW",5,&Size);

            BitBlt(
                PaintStruct.hdc,
                BORDER,
                y + ((Size.cy - bitmap.bmHeight) / 2) + BORDER,
                bitmap.bmWidth,
                bitmap.bmHeight,
                MemoryDC,
                0,0,
                0x220326        // (NOT src) AND dest [DSna]
                );
        }

        //
        // Point to next line's text.
        //
        p += Length + 1;
        y += yDelta;
    }

    //
    // Clean up.
    //
    if(OldFont) {
        SelectObject(PaintStruct.hdc,OldFont);
    }

    if(MemoryDC) {
        if(OldBitmap) {
            SelectObject(MemoryDC,OldBitmap);
        }
        if(Bitmap) {
            DeleteObject(Bitmap);
        }
        DeleteDC(MemoryDC);
    }

    EndPaint(hwnd,&PaintStruct);
}


LRESULT
ActionItemListWndProc(
    IN HWND   hwnd,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    LRESULT rc;
    HFONT OldFont,Font,BoldFont;
    LOGFONT LogFont;
    PWSTR Text;
    PWSTR p;
    UINT LineCount;
    BOOL FreeFont,FreeBoldFont;

    switch(msg) {

    case WM_CREATE:

        //
        // Create fonts.
        //
        OldFont = (HFONT)SendMessage(GetParent(hwnd),WM_GETFONT,0,0);
        if(!OldFont) {
            //
            // Using system font.
            //
            OldFont = GetStockObject(DEFAULT_GUI_FONT);
        }

        FreeFont = TRUE;
        FreeBoldFont = TRUE;
        if(OldFont && GetObject(OldFont,sizeof(LOGFONT),&LogFont)) {

            LogFont.lfWeight = 400;
            Font = CreateFontIndirect(&LogFont);
            if(!Font) {
                Font = GetStockObject(DEFAULT_GUI_FONT);
                FreeFont = FALSE;
            }

            LogFont.lfWeight = 700;
            BoldFont = CreateFontIndirect(&LogFont);
            if(!BoldFont) {
                BoldFont = Font;
                FreeBoldFont = FALSE;
            }
        }

        SetWindowLong(hwnd,AIL_FONT,(LONG)Font);
        SetWindowLong(hwnd,AIL_BOLDFONT,(LONG)BoldFont);
        SetWindowLong(hwnd,AIL_BOLDITEM,0);
        SetWindowLong(hwnd,AIL_TEXT,0);
        SetWindowLong(hwnd,AIL_LINECOUNT,0);
        SetWindowLong(hwnd,AIL_FREEFONTS,MAKELONG(FreeFont,FreeBoldFont));

        rc = 0;
        break;

    case WM_DESTROY:
        //
        // Get rid of fonts we created if necessary.
        //
        FreeFont = (BOOL)GetWindowLong(hwnd,AIL_FREEFONTS);
        FreeBoldFont = HIWORD(FreeFont);
        FreeFont = LOWORD(FreeFont);

        if(FreeFont && (Font = (HFONT)GetWindowLong(hwnd,AIL_FONT))) {
            DeleteObject(Font);
        }

        if(FreeBoldFont && (BoldFont = (HFONT)GetWindowLong(hwnd,AIL_BOLDFONT))) {
            DeleteObject(BoldFont);
        }

        if(Text = (PWSTR)GetWindowLong(hwnd,AIL_TEXT)) {
            MyFree(Text);
        }
        rc = 0;
        break;

    case WM_SETTEXT:
        //
        // Free old text and remember new text.
        //
        if(Text = (PWSTR)GetWindowLong(hwnd,AIL_TEXT)) {
            MyFree(Text);
        }

        LineCount = 0;
        if(Text = DuplicateString((PVOID)lParam)) {
            //
            // Count lines in the text. This is equal to the number of
            // newlines. We require that the last line have a newline
            // to be counted.
            //
            for(LineCount=0,p=Text; *p; p++) {

                if(*p == L'\r') {
                    *p = L' ';
                } else {
                    if(*p == L'\n') {
                        *p = 0;
                        LineCount++;
                    }
                }
            }
        }

        //
        // Cheat a little: we expect wParam to be the 0-based index
        // of the boldfaced line. Callers will have to use SendMessage
        // instead of SetWindowText().
        //
        SetWindowLong(hwnd,AIL_BOLDITEM,(LONG)wParam);
        SetWindowLong(hwnd,AIL_LINECOUNT,LineCount);
        SetWindowLong(hwnd,AIL_TEXT,(LONG)Text);

        rc = (Text != NULL);
        break;

    case WM_PAINT:

        ActionItemListPaint(hwnd);
        rc = 0;
        break;

    default:
        rc = DefWindowProc(hwnd,msg,wParam,lParam);
        break;
    }

    return(rc);
}


BOOL
RegisterActionItemListControl(
    IN BOOL Init
    )
{
    WNDCLASS wc;
    BOOL b;
    static BOOL Registered;

    if(Init) {
        if(Registered) {
            b = TRUE;
        } else {
            wc.style = CS_PARENTDC;
            wc.lpfnWndProc = ActionItemListWndProc;
            wc.cbClsExtra = 0;
            wc.cbWndExtra = AIL_EXTRA * sizeof(LONG);
            wc.hInstance = MyModuleHandle;
            wc.hIcon = NULL;
            wc.hCursor = LoadCursor(NULL,IDC_ARROW);
            wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
            wc.lpszMenuName = NULL;
            wc.lpszClassName = szActionItemListClassName;

            if(b = (RegisterClass(&wc) != 0)) {
                Registered = TRUE;
            }
        }
    } else {
        if(Registered) {
            if(b = UnregisterClass(szActionItemListClassName,MyModuleHandle)) {
                Registered = FALSE;
            }
        } else {
            b = TRUE;
        }
    }

    return(b);
}