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

#include "hierdraw.h"


VOID HierDraw_DrawTerm(LPHEIRDRAWSTRUCT lpHierDrawStruct) {
   if (lpHierDrawStruct->hbmIcons) {
       if (lpHierDrawStruct->hbmMem)
           SelectObject(lpHierDrawStruct->hdcMem,lpHierDrawStruct->hbmMem);
       lpHierDrawStruct->hbmMem = NULL;
       DeleteObject(lpHierDrawStruct->hbmIcons);
       lpHierDrawStruct->hbmIcons = NULL;
   }

   if ( lpHierDrawStruct->hdcMem ) {
      DeleteDC(lpHierDrawStruct->hdcMem);
      lpHierDrawStruct->hdcMem = NULL;
   }
} // HierDraw_DrawTerm

VOID HierDraw_DrawCloseAll(LPHEIRDRAWSTRUCT lpHierDrawStruct ) {
   lpHierDrawStruct->NumOpened= 0;
   if ( lpHierDrawStruct->Opened ) {
      _ffree(lpHierDrawStruct->Opened);
   }
   lpHierDrawStruct->Opened = NULL;
} // HierDraw_DrawCloseAll

VOID HierDraw_OnMeasureItem(HWND hwnd, MEASUREITEMSTRUCT FAR* lpMeasureItem,
                            LPHEIRDRAWSTRUCT lpHierDrawStruct) {
   lpMeasureItem->itemHeight = max(lpHierDrawStruct->nBitmapHeight,
                                   lpHierDrawStruct->nTextHeight);
} // HierDraw_OnMeasureItem

VOID HierDraw_DrawSetTextHeight (HWND hwndList, HFONT hFont, LPHEIRDRAWSTRUCT lpHierDrawStruct ) {
   TEXTMETRIC      TextMetrics;
   HANDLE          hOldFont=NULL;
   HDC             hdc;

   //
   // This sure looks like a lot of work to find the character height
   //
   hdc = GetDC(hwndList);

   hOldFont = SelectObject(hdc, hFont);
   GetTextMetrics(hdc, &TextMetrics);
   SelectObject(hdc, hOldFont);
   ReleaseDC(hwndList, hdc);

   lpHierDrawStruct->nTextHeight = TextMetrics.tmHeight;

   lpHierDrawStruct->nLineHeight =
         max(lpHierDrawStruct->nBitmapHeight, lpHierDrawStruct->nTextHeight);

   if ( hwndList != NULL )
       SendMessage(hwndList, LB_SETITEMHEIGHT, 0,
                   MAKELPARAM(lpHierDrawStruct->nLineHeight, 0));
} // HierDraw_DrawSetTextHeight

static DWORD near RGB2BGR(DWORD rgb) {
    return RGB(GetBValue(rgb),GetGValue(rgb),GetRValue(rgb));
} // RGB2BGR

/*
 *  Creates the objects used while drawing the tree.  This may be called
 *  repeatedly in the event of a WM_SYSCOLORCHANGED message.
 *
 *  WARNING: the Tree icons bitmap is assumed to be a 16 color DIB!
 */

BOOL HierDraw_DrawInit(HINSTANCE hInstance,
                       int  nBitmap,
                       int  nRows,
                       int  nColumns,
                       BOOL bLines,
                       LPHEIRDRAWSTRUCT lpHierDrawStruct,
                       BOOL bInit) {
    HANDLE hRes;
    HANDLE hResMem;
    LPBITMAPINFOHEADER lpbiReadOnly;
    LPBITMAPINFOHEADER lpbiReadWrite;
    DWORD *lpColorTable;
    LPSTR lpBits;
    int biSize;
    int bc;
    HDC hDC;

    if ( bInit ) {
       lpHierDrawStruct->NumOpened = 0;
       lpHierDrawStruct->Opened = NULL;
       lpHierDrawStruct->bLines = bLines;
    }

    //
    // If the Memory DC is not created yet do that first.
    //
    if (!lpHierDrawStruct->hdcMem) {
        //
        // get a screen DC
        //
        hDC = GetDC(NULL);
        //
        // Create a memory DC compatible with the screen
        //
        lpHierDrawStruct->hdcMem = CreateCompatibleDC(hDC);
        //
        // Release the Screen DC
        ReleaseDC(NULL,hDC);

        if (!lpHierDrawStruct->hdcMem)
            return FALSE;

        lpHierDrawStruct->hbmMem = NULL;
    }

    //
    // (Re)Load the Bitmap ( original from disk )
    //
    // Use the FindResource,LoadResource,LockResource since it makes
    // it easy to get the pointer to the BITMAPINFOHEADER we need.
    //
    //
    hRes = FindResource(hInstance, MAKEINTRESOURCE(nBitmap), RT_BITMAP);
    if (!hRes)
        return FALSE;

    hResMem = LoadResource(hInstance, hRes);
    if (!hResMem)
        return FALSE;

    // Now figure out the bitmaps background color.
    // This code assumes the lower left corner is a
    // bit in the background color.
    lpbiReadOnly = (LPBITMAPINFOHEADER)LockResource(hResMem);
    if (!lpbiReadOnly)
        return FALSE;

    // Determine size of bitmap information header plus color table entries
    biSize = lpbiReadOnly->biSize + ((1 << (lpbiReadOnly->biBitCount)) * sizeof(RGBQUAD));

    // Allocate copy of the bitmap information to munge on
    lpbiReadWrite = (LPBITMAPINFOHEADER)GlobalAlloc(GPTR, biSize);
    if (!lpbiReadWrite)
        return FALSE;

    memcpy(lpbiReadWrite, lpbiReadOnly, biSize);

    // Color table immediately follows bitmap information header
    lpColorTable = (DWORD FAR *)((LPBYTE)lpbiReadWrite + lpbiReadWrite->biSize);

    // No need to munge bits so use original
    lpBits = (LPBYTE)lpbiReadOnly + biSize;

    bc = (lpBits[0] & 0xF0) >> 4;   // ASSUMES LOWER LEFT CORNER IS BG!!

    lpColorTable[bc] = RGB2BGR(GetSysColor(COLOR_WINDOW));

    hDC = GetDC(NULL);

    lpHierDrawStruct->hbmIcons = CreateDIBitmap(
                                    hDC,
                                    lpbiReadWrite,
                                    CBM_INIT,
                                    lpBits,
                                    (LPBITMAPINFO)lpbiReadWrite,
                                    DIB_RGB_COLORS
                                    );

    ReleaseDC(NULL,hDC);

    lpHierDrawStruct->nBitmapHeight = (WORD)lpbiReadWrite->biHeight / nRows;
    lpHierDrawStruct->nBitmapWidth = (WORD)lpbiReadWrite->biWidth / nColumns;

    lpHierDrawStruct->nLineHeight =
         max(lpHierDrawStruct->nBitmapHeight, lpHierDrawStruct->nTextHeight);

    GlobalFree(lpbiReadWrite);
    UnlockResource(hResMem);
    FreeResource(hResMem);

    if (!lpHierDrawStruct->hbmIcons)
        return FALSE;

    lpHierDrawStruct->hbmMem = SelectObject(lpHierDrawStruct->hdcMem,lpHierDrawStruct->hbmIcons);
    if (!lpHierDrawStruct->hbmMem)
        return FALSE;

    return TRUE;
} // HierDraw_DrawInit



VOID HierDraw_OnDrawItem(HWND  hwnd,
                         const DRAWITEMSTRUCT FAR* lpDrawItem,
                         int   nLevel,
                         DWORD dwConnectLevel,
                         TCHAR  *szText,
                         int   nRow,
                         int   nColumn,
                         LPHEIRDRAWSTRUCT lpHierDrawStruct) {
    HDC        hDC;
    WORD       wIndent, wTopBitmap, wTopText;
    RECT       rcTemp;


    if ( lpDrawItem->itemID == (UINT)-1 )
       return ;

    hDC = lpDrawItem->hDC;
    CopyRect(&rcTemp, &lpDrawItem->rcItem);

    wIndent = rcTemp.left + ((int)(nLevel) * lpHierDrawStruct->nBitmapWidth) + XBMPOFFSET;
    rcTemp.left = wIndent + lpHierDrawStruct->nBitmapWidth;
    wTopText = rcTemp.top + ((rcTemp.bottom - rcTemp.top) / 2) - (lpHierDrawStruct->nTextHeight / 2);
    wTopBitmap = rcTemp.top + ((rcTemp.bottom - rcTemp.top) / 2) - (lpHierDrawStruct->nBitmapHeight / 2);

    if (lpDrawItem->itemAction == ODA_FOCUS)
        goto DealWithFocus;
    else if (lpDrawItem->itemAction == ODA_SELECT)
        goto DealWithSelection;

    //
    // Draw some lions, if we like lions
    //

    if (lpHierDrawStruct->bLines && nLevel)
      {
        DWORD    dwMask = 1;
        int      nTempLevel;
        int      x,y;

        // draw lines in text color
        SetBkColor(hDC,GetSysColor(COLOR_WINDOWTEXT));

        //
        // Draw a series of | lines for outer levels
        //

        x = lpHierDrawStruct->nBitmapWidth/2 + XBMPOFFSET;

        for ( nTempLevel = 0; nTempLevel < nLevel ; nTempLevel++)
          {
            if ( dwConnectLevel & dwMask )
                FastRect(hDC,x,rcTemp.top,1,rcTemp.bottom - rcTemp.top);

            x += lpHierDrawStruct->nBitmapWidth;
            dwMask *= 2;
          }


        //
        // Draw the short vert line up towards the parent
        //
        nTempLevel = nLevel-1;
        dwMask *= 2;

        x = nTempLevel * lpHierDrawStruct->nBitmapWidth + lpHierDrawStruct->nBitmapWidth / 2 + XBMPOFFSET;

        if ( dwConnectLevel & dwMask )
            y = rcTemp.bottom;
        else
            y = rcTemp.bottom - lpHierDrawStruct->nLineHeight / 2;

        FastRect(hDC,x,rcTemp.top,1,y-rcTemp.top);

        //
        // Draw short horiz bar to right
        //
        FastRect(hDC,x,rcTemp.bottom-lpHierDrawStruct->nLineHeight/2,lpHierDrawStruct->nBitmapWidth/2,1);
      }

    //
    // Draw the selected bitmap
    //

    BitBlt(hDC,
           wIndent,wTopBitmap,
           lpHierDrawStruct->nBitmapWidth,lpHierDrawStruct->nBitmapHeight,
           lpHierDrawStruct->hdcMem,
           nColumn*lpHierDrawStruct->nBitmapWidth,
           nRow*lpHierDrawStruct->nBitmapHeight,
           SRCCOPY);

DealWithSelection:

    if (lpDrawItem->itemState & ODS_SELECTED)
      {
        SetBkColor(hDC,GetSysColor(COLOR_HIGHLIGHT));
        SetTextColor(hDC,GetSysColor(COLOR_HIGHLIGHTTEXT));
      }
    else
      {
        SetBkColor(hDC,GetSysColor(COLOR_WINDOW));
        SetTextColor(hDC,GetSysColor(COLOR_WINDOWTEXT));
      }


    ExtTextOut(hDC, rcTemp.left + 1, wTopText, ETO_CLIPPED|ETO_OPAQUE,
               &rcTemp,szText,lstrlen(szText), NULL);

    if (lpDrawItem->itemState & ODS_FOCUS && lpDrawItem->itemAction != ODA_SELECT) {
DealWithFocus:
        DrawFocusRect(hDC, &rcTemp);
    }


} // HierDraw_OnDrawItem


//
// draw a solid color rectangle quickly
//
static VOID near FastRect(HDC hDC, int x, int y, int cx, int cy) {
    RECT rc;

    rc.left = x;
    rc.right = x+cx;
    rc.top = y;
    rc.bottom = y+cy;
    ExtTextOut(hDC,x,y,ETO_OPAQUE,&rc,NULL,0,NULL);
} // FastRect


BOOL HierDraw_IsOpened(LPHEIRDRAWSTRUCT lpHierDrawStruct, DWORD dwData) {
   // For Now just a dumb  search
   //
   int Count;

   for ( Count = 0; Count < lpHierDrawStruct->NumOpened; Count++ ) {
     if ( lpHierDrawStruct->Opened[Count] == dwData ) {
        return TRUE;
     }
   }

   return FALSE;

} // HierDraw_IsOpened


VOID HierDraw_OpenItem(LPHEIRDRAWSTRUCT lpHierDrawStruct, DWORD dwData) {
    lpHierDrawStruct->NumOpened++;

    if (lpHierDrawStruct->Opened == NULL )
       lpHierDrawStruct->Opened =
        (DWORD FAR *)_fmalloc(sizeof(DWORD)*lpHierDrawStruct->NumOpened);
    else
       lpHierDrawStruct->Opened =
        (DWORD FAR *)_frealloc(lpHierDrawStruct->Opened,
               sizeof(DWORD)*lpHierDrawStruct->NumOpened);

    lpHierDrawStruct->Opened[lpHierDrawStruct->NumOpened-1] = dwData;
} // HierDraw_OpenItem


VOID HierDraw_CloseItem(LPHEIRDRAWSTRUCT lpHierDrawStruct, DWORD dwData) {
   // For Now just a dumb  search
   //
   int Count;

   for ( Count = 0; Count < lpHierDrawStruct->NumOpened; Count++ ) {
     if ( lpHierDrawStruct->Opened[Count] == dwData ) {
        if (--lpHierDrawStruct->NumOpened == 0 ) {
            _ffree(lpHierDrawStruct->Opened);
            lpHierDrawStruct->Opened = NULL;
        }
        else {
            if ( Count < lpHierDrawStruct->NumOpened ) {
               _fmemmove(&(lpHierDrawStruct->Opened[Count]),
                     &(lpHierDrawStruct->Opened[Count+1]),
                     sizeof(DWORD)*(lpHierDrawStruct->NumOpened-Count));
            }
            lpHierDrawStruct->Opened =
                  (DWORD FAR *)_frealloc(lpHierDrawStruct->Opened,
                     sizeof(DWORD)*lpHierDrawStruct->NumOpened);
        }
     }
   }
} // HierDraw_CloseItem


VOID HierDraw_ShowKids(LPHEIRDRAWSTRUCT lpHierDrawStruct,
                       HWND hwndList, WORD wCurrentSelection, WORD wKids) {
   WORD wBottomIndex;
   WORD wTopIndex;
   WORD wNewTopIndex;
   WORD wExpandInView;
   RECT rc;

   wTopIndex = (WORD)SendMessage(hwndList, LB_GETTOPINDEX, 0, 0L);
   GetClientRect(hwndList, &rc);
   wBottomIndex = wTopIndex + (rc.bottom+1) / lpHierDrawStruct->nLineHeight;

   wExpandInView = (wBottomIndex - wCurrentSelection);

   if (wKids >= wExpandInView) {
        wNewTopIndex = min(wCurrentSelection, wTopIndex + wKids - wExpandInView + 1);
        SendMessage(hwndList, LB_SETTOPINDEX, (WORD)wNewTopIndex, 0L);
   }

} // HierDraw_ShowKids