BITMAP.C
/* 
 -  B I T M A P . C 
 - 
 *  Purpose: 
 *      Bitmap and Listbox support functions for InBox in sample mail client. 
 * 
 *  Copyright 1993-1995 Microsoft Corporation. All Rights Reserved. 
 */ 
 
#include <stdlib.h> 
#include <string.h> 
#include <windows.h> 
#include <windowsx.h> 
#ifdef _WIN32 
#include <objerror.h> 
#include <objbase.h> 
#endif 
#ifdef WIN16 
#include <compobj.h> 
#endif 
#include <mapiwin.h> 
#include <mapidbg.h> 
#include <mapi.h> 
#include <mapix.h> 
#include "bitmap.h" 
#include "client.h" 
 
// Fonts to use in dialogs 
 
#ifdef _WIN32 
#define SHELL_FONT "MS Shell Dlg" 
#define SHELL_FONT_SIZE 8 
#else 
#define SHELL_FONT "MS Sans Serif" 
#define SHELL_FONT_SIZE 8 
#endif 
 
/* 
 *  globals 
 */ 
  
DWORD   rgbWindowColor = 0xFF000000;    // variables for the current 
DWORD   rgbHiliteColor = 0xFF000000;    // system color settings. 
DWORD   rgbWindowText  = 0xFF000000;    // on a WM_SYSCOLORCHANGE 
DWORD   rgbHiliteText  = 0xFF000000;    // we check to see if we need 
DWORD   rgbGrayText    = 0xFF000000;    // to reload our bitmap. 
DWORD   rgbDDWindow    = 0xFF000000;    // 
DWORD   rgbDDHilite    = 0xFF000000;    // 0xFF000000 is an invalid RGB 
 
// an array of integers containing the tab stops, in pixels. The tab  
// stops must be sorted in ascending order; back tabs are not allowed.  
 
int     rgTabs[] = { 2, 28, 135, 292 }; 
int     dxbmpLB, dybmpLB;   // dx and dy of listbox bmps 
 
HDC     hdcMemory = 0;      // hdc to hold listbox bitmaps (for speed) 
HBITMAP hbmpOrigMemBmp = 0; // original null bitmap in hdcMemory 
HBITMAP hbmpLB = 0;         // cached listbox bitmaps 
HFONT   hfontLB = 0;        // hfont of LB 
HWND    hwndLB = 0;         // hwnd of LB 
 
FONTSTYLE fontStyle = { SHELL_FONT_SIZE, FW_NORMAL, 0, TEXT(SHELL_FONT) }; 
 
extern HANDLE hInst; 
 
 
/* 
 -  DeInitBmps 
 -   
 *  Purpose: 
 *      cleans up LB hfonts, hdc, and hbmps 
 */ 
  
VOID DeInitBmps(VOID) 
{ 
    DeleteBitmapLB(); 
    if(hdcMemory) 
    { 
        DeleteDC(hdcMemory); 
        hdcMemory = 0; 
    } 
 
    if(hfontLB) 
    { 
        SetWindowFont(hwndLB, GetStockObject(SYSTEM_FONT), FALSE); 
        DeleteObject(hfontLB); 
        hfontLB = 0; 
    } 
} 
 
 
/* 
 -  SetLBFont 
 -   
 *  Purpose: 
 *      creates a font from the global fontStyle 
 *      sets global hfontLB to new font and WM_SETFONTs 
 *      the hwndLB to the new font 
 */ 
  
VOID SetLBFont(VOID) 
{ 
    LOGFONT lf; 
 
    lf.lfHeight = fontStyle.lfHeight; 
    lf.lfWidth = 0; 
    lf.lfEscapement = 0; 
    lf.lfOrientation = 0; 
    lf.lfWeight = fontStyle.lfWeight; 
    lf.lfItalic = fontStyle.lfItalic; 
    lf.lfUnderline = 0; 
    lf.lfStrikeOut = 0; 
    lf.lfCharSet = ANSI_CHARSET; 
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS; 
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; 
    lf.lfQuality = DEFAULT_QUALITY; 
    lf.lfPitchAndFamily = DEFAULT_PITCH | FF_SWISS; 
    lstrcpy(lf.lfFaceName, fontStyle.lfFaceName); 
 
    hfontLB = CreateFontIndirect(&lf); 
    if(hfontLB) 
        SetWindowFont(hwndLB, hfontLB, FALSE);         
} 
 
 
/* 
 -  InitBmps 
 -   
 *  Purpose: 
 *      inits listbox globals, creates listbox 
 *   
 *  Arguments: 
 *      HWND    main hwnd of app (parent of LB) 
 *   
 *  Returns: 
 *      TRUE - success; FALSE - failed 
 */ 
  
BOOL InitBmps(HWND hwnd, int idLB) 
{ 
    HDC     hdcScreen; 
    HBITMAP hbmpTemp; 
 
    hdcScreen = GetDC(0); 
    if(!hdcScreen) 
        goto CantInit; 
    hdcMemory = CreateCompatibleDC(hdcScreen); 
    if(!hdcMemory) 
        goto ReleaseScreenDC; 
 
    hbmpTemp = CreateCompatibleBitmap(hdcMemory, 1, 1); 
    if(!hbmpTemp) 
        goto ReleaseMemDC; 
    hbmpOrigMemBmp = SelectObject(hdcMemory, hbmpTemp); // get hbmp of NULL 
    if(!hbmpOrigMemBmp)                                 // bmp for hdcMemory 
        goto ReleaseMemDC;                              // for when we delete 
    SelectObject(hdcMemory, hbmpOrigMemBmp);            // it later in life 
    DeleteObject(hbmpTemp); 
    ReleaseDC(0, hdcScreen); 
 
    SetRGBValues();     // set the global RGB values 
    LoadBitmapLB();     // load the bmps into hdcMemory 
 
    hwndLB = GetDlgItem(hwnd, idLB); 
     
    SetLBFont();    // set the font of our listbox 
    return TRUE; 
 
/* Error recovery exits */ 
ReleaseMemDC: 
    DeleteDC(hdcMemory); 
    hdcMemory = 0; 
 
ReleaseScreenDC: 
    ReleaseDC(0, hdcScreen); 
 
CantInit: 
    return FALSE; 
} 
 
 
/* 
 -  SetRGBValues 
 -   
 *  Purpose: 
 *      To set various system colors in static variables.  Called at 
 *      init time and when system colors change. 
 */ 
  
VOID SetRGBValues(VOID) 
{ 
    rgbWindowColor = GetSysColor(COLOR_WINDOW); 
    rgbHiliteColor = GetSysColor(COLOR_HIGHLIGHT); 
    rgbWindowText  = GetSysColor(COLOR_WINDOWTEXT); 
    rgbHiliteText  = GetSysColor(COLOR_HIGHLIGHTTEXT); 
    rgbGrayText    = GetSysColor(COLOR_GRAYTEXT); 
} 
 
 
/* 
 -  MeasureItem 
 -   
 *  Purpose: 
 *      called from msg WM_MEASUREITEM: returns max dy of listbox items 
 *   
 *  Arguments: 
 *      HWND        hwnd of main window 
 *      pmis        measureitemstruct from WM_MEASUREITEM call 
 */ 
  
VOID MeasureItem(HANDLE hwnd, LPMEASUREITEMSTRUCT pmis) 
{ 
    HDC        hDC = GetDC(hwnd); 
    HANDLE     hFont = hfontLB; 
    TEXTMETRIC TM; 
 
    if(!hFont) 
        hFont = GetStockObject(SYSTEM_FONT); 
    hFont = SelectObject(hDC, hFont); 
    GetTextMetrics(hDC, &TM); 
    SelectObject(hDC, hFont); 
    ReleaseDC(hwnd, hDC); 
 
    // set the height to be max of (dyfont or dybitmap) 
    pmis->itemHeight = max(dybmpLB, TM.tmHeight); 
} 
 
 
/* 
 -  OutTextFormat 
 -   
 *  Purpose: 
 *      to parse the string in the listbox and draw it accordingly: 
 *      first char == chBOLD: line is bold 
 *      first char == chUNDERLINE: line is underlined (can follow chBOLD) 
 *      char == chTAB: go to next column in rgTabs 
 *      '/001#': bitblt that numbered bitmap. 
 *      otherwise, outtext the line 
 *   
 *  Arguments: 
 *      pDI     from DrawItem from WM_DRAWITEM msg 
 */ 
  
VOID OutTextFormat(LPDRAWITEMSTRUCT pDI) 
{ 
    TCHAR   szDateRec[32]; 
    TCHAR   szItem[256]; 
    TCHAR   szTemp[4]; 
    TCHAR   szDots[4] = {"..."}; 
    TCHAR   *pch; 
    INT     nT; 
    INT     nTab = 0;           // current tab we is on 
    INT     nBmp;               // index of envelope bitmap 
    HFONT   hfDef = 0; 
    HFONT   hfOld = 0;          // bold or underlined font 
    TCHAR   *pchBuff = NULL; 
    LPMSGID lpMsgId = (LPMSGID)pDI->itemData; 
 
    pch = szItem; 
 
    // Format a string from the info in lpMsgNode 
    // First, calculate the index to the desired bitmap 
     
    nBmp = ((!lpMsgId->fUnRead) * 2) + ((!!lpMsgId->fHasAttach) * 1 ); 
 
    // Convert our received date and build string 
     
    ConvertDateRec (lpMsgId->lpszDateRec, szDateRec); 
 
    // Limit our subject size 
     
    szTemp[0] = '\0'; 
     
    if(lpMsgId->lpszSubject && (lstrlen(lpMsgId->lpszSubject) > 32)) 
    { 
        memcpy(szTemp, &lpMsgId->lpszSubject[28], 4); 
        memcpy(&lpMsgId->lpszSubject[28], szDots, 4); 
    } 
     
    wsprintf(szItem, "\001%d\t%s\t%s\t%s", nBmp,  
            (lpMsgId->lpszFrom ? lpMsgId->lpszFrom : ""), 
            (lpMsgId->lpszSubject ? lpMsgId->lpszSubject : ""), 
            szDateRec); 
 
    // erase background 
    ExtTextOut(pDI->hDC, 0, 0, ETO_OPAQUE, &pDI->rcItem, NULL, 0, NULL); 
 
    // underline or bold this line?  Only check first & second char 
    if(*pch == chBOLD || *pch == chUNDERLINE) 
    { 
        LOGFONT     lf; 
 
        hfOld = GetWindowFont(pDI->hwndItem); 
        if(!hfOld) 
            hfOld = GetStockObject(SYSTEM_FONT); 
        GetObject(hfOld, sizeof(lf), &lf); 
 
        if(*pch == chBOLD) 
        { 
            lf.lfWeight = FW_BOLD; 
            pch++; 
        } 
        if(*pch == chUNDERLINE) 
        { 
            lf.lfUnderline = TRUE; 
            pch++; 
        } 
 
        hfDef = CreateFontIndirect(&lf); 
        if(hfDef) 
            SelectObject(pDI->hDC, hfDef); 
    } 
 
    // selected or nonselected bmps? 
    nT = (ODS_SELECTED & pDI->itemState) ? (BMWIDTH * NUMBMPS) : 0; 
 
    // parse the string 
    for(; *pch; pch++) 
    { 
        TCHAR   *pchT; 
        RECT    rc; 
 
        if(*pch == chBITMAP)     // do we have a bitmap? 
        { 
            ++pch; 
            // draw the bitmap 
            BitBlt(pDI->hDC, pDI->rcItem.left + rgTabs[nTab], 
                pDI->rcItem.top, BMWIDTH, BMHEIGHT, hdcMemory, 
                nT + (int)(*pch - TEXT('0')) * BMWIDTH, 0, SRCCOPY); 
            continue; 
        } 
 
        if(*pch == chTAB)    // move to next tabstop? 
        { 
            nTab++; 
            continue; 
        } 
 
        pchT = pch;     // find end of the column of text 
        while(*pchT && (*pchT != chTAB)) 
            pchT++; 
 
        // set rect to drawtext in 
        SetRect(&rc, pDI->rcItem.left + rgTabs[nTab], pDI->rcItem.top,  
            pDI->rcItem.right, pDI->rcItem.bottom); 
 
        // draw the text 
        ExtTextOut(pDI->hDC, rc.left, rc.top + 1, ETO_OPAQUE | ETO_CLIPPED, 
            &rc, pch, pchT - pch, NULL); 
        pch = pchT - 1; // move to end of this column 
    } 
 
    if(hfDef)   // delete underline or bold font if we created it 
    { 
        SelectObject(pDI->hDC, hfOld); 
        DeleteObject(hfDef); 
    } 
 
    if(szTemp[0] != '\0') 
    { 
        memcpy(&lpMsgId->lpszSubject[28], szTemp, 4); 
    } 
} 
 
 
/* 
 -  DrawItem 
 - 
 *  Purpose: 
 *      Handles WM_DRAWITEM for both drive and directory listboxes. 
 * 
 *  Parameters: 
 *      pDI     LPDRAWITEMSTRUCT passed from the WM_DRAWITEM message. 
 */ 
  
VOID DrawItem(LPDRAWITEMSTRUCT pDI) 
{ 
    COLORREF    crText, crBack; 
 
    if((int)pDI->itemID < 0) 
        return; 
 
    if((ODA_DRAWENTIRE | ODA_SELECT) & pDI->itemAction) 
    { 
        if(pDI->itemState & ODS_SELECTED) 
        { 
            // Select the appropriate text colors 
            crText = SetTextColor(pDI->hDC, rgbHiliteText); 
            crBack = SetBkColor(pDI->hDC, rgbHiliteColor); 
        } 
 
        // parse and spit out bmps and text 
        OutTextFormat(pDI); 
 
        // Restore original colors if we changed them above. 
        if(pDI->itemState & ODS_SELECTED) 
        { 
            SetTextColor(pDI->hDC, crText); 
            SetBkColor(pDI->hDC,   crBack); 
        } 
    } 
 
    if((ODA_FOCUS & pDI->itemAction) || (ODS_FOCUS & pDI->itemState)) 
        DrawFocusRect(pDI->hDC, &pDI->rcItem); 
} 
 
 
/* 
 -  ConvertDateRec 
 - 
 *  Purpose: 
 *      To convert the lpszDateReceived field of a message to a 
 *      more paletable display format; namely: mm/dd/yy hh:mmAM. 
 * 
 *  Parameters: 
 *      lpszDateRec         - Original format 
 *      lpszDateDisplay     - Display format 
 */ 
 
VOID ConvertDateRec (LPSTR lpszDateRec, LPSTR lpszDateDisplay) 
{ 
    char  szDateTmp[32]; 
    LPSTR lpszYear; 
    LPSTR lpszMonth; 
    LPSTR lpszDay; 
    LPSTR lpszHour; 
    LPSTR lpszMinute; 
    int nHour; 
    static char szFoo[2][3] = 
    {"AM", "PM"}; 
 
    *lpszDateDisplay = 0; 
    if (!lpszDateRec || !*lpszDateRec) 
        return; 
 
    lstrcpy(szDateTmp, lpszDateRec); 
 
    lpszYear = strtok (szDateTmp, "/ :"); 
    lpszMonth = strtok (NULL, "/ :"); 
    lpszDay = strtok (NULL, "/ :"); 
    lpszHour = strtok (NULL, "/ :"); 
    lpszMinute = strtok (NULL, "/ :"); 
 
    if(lpszHour) 
        nHour = atoi (lpszHour); 
    else 
        nHour = 0; 
 
    if (nHour > 12) 
        wsprintf (lpszHour, "%d", nHour - 12); 
 
    wsprintf (lpszDateDisplay, "%s/%s/%s %s:%s%s", lpszMonth, 
        (lpszDay ? lpszDay : ""), 
        (lpszYear ? lpszYear : ""), 
        (lpszHour ? lpszHour : ""), 
        (lpszMinute ? lpszMinute : ""), 
        szFoo[(nHour > 11 ? 1 : 0)]); 
} 
 
 
/* 
 *  RgbInvertRgb 
 *   
 *  Purpose: 
 *      To reverse the byte order of the RGB value (for file format 
 *   
 *  Arguments: 
 *   
 *  Returns: 
 *      New color value (RGB to BGR) 
 */ 
  
#define RgbInvertRgb(_rgbOld) \ 
    (DWORD)RGB(GetBValue(_rgbOld), GetGValue(_rgbOld), GetRValue(_rgbOld)) 
 
 
/* 
 *  LoadAlterBitmap (mostly stolen from commdlg) 
 *   
 *  Purpose: 
 *      Loads the IDB_ENVELOPE bitmap and gives all the pixels that are 
 *      RGBREPLACE a new color. 
 * 
 *  Assumption: 
 *      This function will work on one bitmap during it's lifetime. 
 *      (Due to the fact that it finds RGBREPLACE once and then 
 *      operates on that offset whenever called again because under NT, 
 *      it appears that the bitmap is cached, so the second time you go 
 *      looking for RGBREPLACE, it won't be found.) You could load the 
 *      resource, copy it, then modify the copy as a workaround. But I 
 *      chose the cheap way out as I will only ever modify one bmp. 
 *   
 *  Arguments: 
 *      rgbInstead  rgb value to replace defined RGBREPLACE with 
 *   
 *  Returns: 
 *      NULL - failed or hbmp of new modified bitmap 
 */ 
 
HBITMAP LoadAlterBitmap(DWORD rgbInstead) 
{ 
    HANDLE              hbmp = 0; 
    LPBITMAPINFOHEADER  qbihInfo; 
    HDC                 hdcScreen; 
    HRSRC               hresLoad; 
    HGLOBAL             hres; 
    LPBYTE              qbBits; 
    DWORD               rgbReplace = 0; 
    DWORD               *rgdw = NULL; 
    DWORD               *lpdw = NULL; 
    ULONG               cb = 0; 
     
    if (rgbInstead) 
        rgbReplace = RGBREPLACE; 
 
    // load our listbox bmps resource 
    hresLoad = FindResource(hInst, MAKEINTRESOURCE(IDB_ENVELOPE), RT_BITMAP); 
    if(hresLoad == 0) 
        return 0; 
    hres = LoadResource(hInst, hresLoad); 
    if(hres == 0) 
        return 0; 
 
    rgbReplace = RgbInvertRgb(rgbReplace); 
    rgbInstead = RgbInvertRgb(rgbInstead); 
    qbihInfo = (LPBITMAPINFOHEADER)LockResource(hres); 
 
    // Skip over the header structure 
    qbBits = (LPBYTE)(qbihInfo + 1); 
 
    // Skip the color table entries, if any 
    qbBits += (1 << (qbihInfo->biBitCount)) * sizeof(RGBQUAD); 
 
    // Copy the resource into writable memory so we can 
    // munge the color table to set our background color 
    cb = (ULONG)(qbBits - (LPBYTE)qbihInfo) + qbihInfo->biSizeImage; 
    rgdw = (DWORD *)GlobalAllocPtr(GMEM_MOVEABLE, cb); 
     
    CopyMemory((LPVOID)rgdw, (LPVOID)qbihInfo, cb); 
     
    // find the color to replace in the color table 
    for(lpdw = (DWORD *)((LPBYTE)rgdw + qbihInfo->biSize); ; lpdw++) 
    { 
        if(*lpdw == rgbReplace) 
            break; 
    } 
 
    // replace that color value with our new one 
    *lpdw = (DWORD)rgbInstead; 
 
    // Create a color bitmap compatible with the display device 
    hdcScreen = GetDC(0); 
    if(hdcScreen != 0) 
    { 
        hbmp = CreateDIBitmap(hdcScreen, (LPBITMAPINFOHEADER)rgdw,  
                (LONG)CBM_INIT, qbBits, (LPBITMAPINFO) rgdw, DIB_RGB_COLORS); 
        ReleaseDC(0, hdcScreen); 
    } 
 
    UnlockResource(hres); 
    FreeResource(hres); 
 
    GlobalFreePtr(rgdw); 
     
    return hbmp; 
} 
 
 
/* 
 *  DeleteBitmapLB 
 *   
 *  Purpose: 
 *      Get rid of hbmpLB, if it exists 
 */ 
  
VOID DeleteBitmapLB(VOID) 
{ 
    if(hbmpOrigMemBmp) 
    { 
        SelectObject(hdcMemory, hbmpOrigMemBmp); 
        if(hbmpLB != 0) 
        { 
            DeleteObject(hbmpLB); 
            hbmpLB = 0; 
        } 
    } 
} 
 
 
/* 
 *  LoadBitmapLB (mostly stolen from commdlg) 
 *   
 *  Purpose: 
 *      Creates the listbox bitmap. If an appropriate bitmap 
 *      already exists, it just returns immediately.  Otherwise, it 
 *      loads the bitmap and creates a larger bitmap with both regular 
 *      and highlight colors. 
 * 
 *  Returns: 
 *      TRUE - success; FALSE - failure 
 */ 
  
BOOL LoadBitmapLB(VOID) 
{ 
    BITMAP  bmp; 
    HANDLE  hbmp, hbmpOrig; 
    HDC     hdcTemp; 
    BOOL    bWorked = FALSE; 
 
    // check for existing bitmap and validity 
    if( (hbmpLB != 0) && 
        (rgbWindowColor == rgbDDWindow) && 
        (rgbHiliteColor == rgbDDHilite)) 
    { 
        if(SelectObject(hdcMemory, hbmpLB)) 
            return TRUE; 
    } 
 
    DeleteBitmapLB(); 
 
    rgbDDWindow = rgbWindowColor; 
    rgbDDHilite = rgbHiliteColor; 
 
    if(!(hdcTemp = CreateCompatibleDC(hdcMemory))) 
        goto LoadExit; 
 
    if(!(hbmp = LoadAlterBitmap(rgbWindowColor))) 
        goto DeleteTempDC; 
 
    GetObject(hbmp, sizeof(BITMAP), (LPBYTE) &bmp); 
    dybmpLB = bmp.bmHeight; 
    dxbmpLB = bmp.bmWidth; 
 
    hbmpOrig = SelectObject(hdcTemp, hbmp); 
 
    hbmpLB = CreateDiscardableBitmap(hdcTemp, dxbmpLB*2, dybmpLB); 
    if(!hbmpLB) 
        goto DeleteTempBmp; 
 
    if(!SelectObject(hdcMemory, hbmpLB)) 
    { 
        DeleteBitmapLB(); 
        goto DeleteTempBmp; 
    } 
 
    BitBlt(hdcMemory, 0, 0, dxbmpLB, dybmpLB,   // copy unhighlited bmps 
           hdcTemp, 0, 0, SRCCOPY);             // into hdcMemory 
    SelectObject(hdcTemp, hbmpOrig); 
 
    DeleteObject(hbmp); 
 
    if(!(hbmp = LoadAlterBitmap(rgbHiliteColor))) 
        goto DeleteTempDC; 
 
    hbmpOrig = SelectObject(hdcTemp, hbmp); 
    BitBlt(hdcMemory, dxbmpLB, 0, dxbmpLB, dybmpLB, // copy highlited bmps 
        hdcTemp, 0, 0, SRCCOPY);                    // into hdcMemory 
    SelectObject(hdcTemp, hbmpOrig); 
 
    bWorked = TRUE; 
 
DeleteTempBmp: 
    DeleteObject(hbmp); 
DeleteTempDC: 
    DeleteDC(hdcTemp); 
LoadExit: 
    return bWorked; 
}