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;
}