LISTHSCR.C

/* 
* LISTHSCR.C
*
* Added functions to support horizontal listbox scrolling. This
* DLL is generalized to support any listbox. The FInitListboxExtents
* function allocates local memory (from the DLLs DATA segment) for
* the list of string extents to go in the listbox. The local handle
* is then assigned as a property of the window, so every other
* function first looks at this property.
*
* This means that any number of horizontal scrolling listboxes can
* be used in the system and make use of these functions, as long
* as the DLLs memory is not full.
*
*/


#include <windows.h>
#include "listhscr.h"


/*
* This is just the label of the property given to each listbox
* that asks for an extent list.
*/

char szXTList[]="XTList";





/*
* FInitListboxExtents
*
* Purpose:
* Simple helper function to initialize everything for maintaining
* horizontal extents in a listbox. This function allocates memory
* to hold the list of extents and assigns it to the window as a property.
*
* Parameters:
* hList HWND of the listbox concerned.
*
* Return Value:
* BOOL TRUE if the function was successful.
* FALSE if memory could not be allocated.
*/

BOOL FAR PASCAL FInitListboxExtents(HWND hList)
{
HANDLE hMem;
WORD *pw;

/*
* Initially allocate 260 bytes, or 130 WORDs since the majority
* of listbox usage will not require a reallocation, and
* allocating 256 bytes is just as efficient as allocating 2
* bytes, if not more so because of reduces overhead.
*
* The extra two words store the current number of extent entries
* and the maximum number possible in this memory block.
*
*/

hMem=LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, CBALLOCUNIT + sizeof(WORD)<<1);

if (hMem==NULL)
return FALSE;

/*
* Set the first two words in the memory to the appropriate values.
* If we can't lock it we can;'t use it!
*/
pw=(WORD *)LocalLock(hMem);

if (pw==NULL)
{
LocalFree(hMem);
return FALSE;
}

*pw=0; //cExtentEntries
*(pw+1)=CALLOCUNITS; //cExtentEntriesMax

LocalUnlock(hMem);

/*
* Assign the memory handle as a property of the listbox. This allows
* this code to take any hList and get it's extent entry list,
* therefore having full support for multiple listboxes.
*/
SetProp(hList, (LPSTR)szXTList, hMem);
return TRUE;
}





/*
* FFreeListboxExtents
*
* Purpose:
* Release any memory used for storing the extents of the
* horizontal listbox. This MUST be called when the listbox
* is destroyed, like in the WM_DESTROY case of the parent window.
*
* Parameters:
* hList HWND of the listbox concerned.
*
* Return Value:
* BOOL TRUE if the function was successful.
* FALSE if there is an error.
*/

BOOL FAR PASCAL FFreeListboxExtents(HWND hList)
{
HANDLE hMem;
BOOL fSuccess;

//Load the handle to free.
hMem=GetProp(hList, (LPSTR)szXTList);

/*
* Return a BOOL on the result. An app could keep calling this
* function until it worked since hMem is still around.
*/
fSuccess=(BOOL)LocalFree(hMem);

if (fSuccess)
RemoveProp(hList, (LPSTR)szXTList); //Only if handle was freed!

return fSuccess;
}




/*
* ResetListboxExtents
*
* Purpose:
* Deletes all extents in the extent list to be used AFTER an
* LB_RESETCONTENT is sent to the listbox.
*
* Parameters:
* hList HWND of the listbox.
*
* Return Value:
* none
*
*/

void FAR PASCAL ResetListboxExtents(HWND hList)
{
FFreeListboxExtents(hList);
FInitListboxExtents(hList);

SendMessage(hList, LB_SETHORIZONTALEXTENT, 0, 0L);

//This is required to remove the scrollbar.
SendMessage(hList, LB_DELETESTRING, 0, 0L);
return;
}





/*
* WAddExtentEntry
*
* Purpose:
* Facilitates handling of the horizontal listbox by keeping
* track of the pixel width of the longest string in the listbox.
* The number of pixels that the listbox scrolls is the width
* of the longest string.
*
* Parameters:
* hList HWND of the listbox.
* psz Pointer to string that is added. This must be passed
* instead of an index into the listbox since this must
* be called before the string is added if the scrollbar
* is to be maintained properly.
*
* Return Value:
* WORD 0 if the string added was not the longest string in
* the listbox and therefore did not change the visibility
* of the horizontal scrollbar.
*
* wExtent if the added string was the longest, thus either
* making the scrollbar visible or changing the extent.
*
* -1 on an error.
*
*/

WORD FAR PASCAL WAddExtentEntry(HWND hList, LPSTR psz)
{
HANDLE hMem;
WORD cExtentEntries;
WORD cExtentEntriesMax;
WORD *pw; //Pointer to extent memory.
WORD wExtent;
WORD i=0;
WORD iRev;


hMem=GetProp(hList, (LPSTR)szXTList);

if (hMem==NULL)
return ((WORD)-1);


pw=(WORD *)LocalLock(hMem);

if (pw==NULL)
return ((WORD)-1);

//Load the values and set pointer to start of list.
cExtentEntries=*pw++;
cExtentEntriesMax=*pw++;

//Reallocate if necessary.
if (cExtentEntries==cExtentEntriesMax)
{
LocalUnlock(hMem);

//This call takes care of cExtentEntriesMax
if (!FReAllocExtentList(hMem, TRUE))
return ((WORD)-1);

cExtentEntriesMax += CALLOCUNITS;
pw=(WORD *)LocalLock(hMem);

if (pw==NULL)
return ((WORD)-1);

pw+=2; //Skip the two counters.
}

wExtent=WGetListboxStringExtent(hList, psz);


/*
* Insert the new extent into the list. This list is just a sorted
* list (descending) of the largest to smallest extents in the
* listbox. When deleting a string, we just need to look in this
* list for it's extent and remove that entry.
*
* Yeah, this can be inefficient, but this is not a real case for
* optimization.
*
*/

if (cExtentEntries==0)
pw[0]=wExtent;
else
{
i=IFindExtentInList(pw, wExtent, cExtentEntries);

for (iRev=cExtentEntries+1; iRev > i; iRev--)
pw[iRev]=pw[iRev-1];

pw[i]=wExtent;
}

cExtentEntries++;

//Save these values back. pw must be decremented first.
*(--pw)=cExtentEntriesMax;
*(--pw)=cExtentEntries;

LocalUnlock(hMem);


/*
* Check if the one we added is now the first. If so, then
* we need to reset the horizontal extent.
*/

if (i==0)
{
SendMessage(hList, LB_SETHORIZONTALEXTENT, wExtent, 0L);
return wExtent;
}

return ((WORD)0);
}








/*
* WRemoveExtentEntry
*
* Purpose:
* Facilitates handling of the horizontal listbox by keeping
* track of the pixel width of the longest string in the listbox.
* The number of pixels that the listbox scrolls is the width
* of the longest string.
*
* Parameters:
* hList HWND of the listbox.
* iSel WORD index of the string to be removed.
*
* Return Value:
* WORD 0 if the string removed did not affect the visibilty
* of the horizontal scrollbar, i.e. if there still is
* a longer string or there is no scrollbar in the first
* place.
*
* wExtent of the new longest string if the one removed
* was the longest.
*
* -1 on an error.
*/

WORD FAR PASCAL WRemoveExtentEntry(HWND hList, WORD iSel)
{
WORD *pw; //Pointer to extent memory.
WORD cExtentEntries;
WORD cExtentEntriesMax;
WORD wExtent;
WORD i;
WORD iSave;
HANDLE hMem;
HANDLE hMemT;
char *pch;
WORD cb;


hMem=GetProp(hList, (LPSTR)szXTList);

if (hMem==NULL)
return ((WORD)-1);


pw=(WORD *)LocalLock(hMem);

if (pw==NULL)
return ((WORD)-1);

//Load the values and set pointer to start of list.
cExtentEntries=*pw++;
cExtentEntriesMax=*pw++;

if (cExtentEntries==0)
{
LocalUnlock(hMem);
return ((WORD)-1);
}


//Free up memory if necessary. No reallocating smaller is not fatal.
if ((cExtentEntriesMax-cExtentEntries)==CALLOCUNITS)
{
LocalUnlock(hMem);

if (!FReAllocExtentList(hMem, FALSE))
return ((WORD)-1);

cExtentEntriesMax += CALLOCUNITS;
pw=(WORD *)LocalLock(hMem);

if (pw==NULL)
return ((WORD)-1);

pw+=2; //Skip the two counters.
}

cb=(WORD)SendMessage(hList, LB_GETTEXTLEN, iSel, 0L);

//Temporary memory to copy the listbox string so we can get the extent.
hMemT=LocalAlloc(LMEM_MOVEABLE, cb+2); //One extra to be safe.
pch=LocalLock(hMemT);

if (pch==NULL)
{
LocalUnlock(hMem);
LocalFree(hMemT);
return ((WORD)-1);
}

cb=(WORD)SendMessage(hList, LB_GETTEXT, iSel, (LONG)(LPSTR)pch);

wExtent=WGetListboxStringExtent(hList, (LPSTR)pch);

LocalUnlock(hMemT);
LocalFree(hMemT);


/*
* Find the extent in the list and remove it. If it's the first,
* then reset the horizontal extent to the second.
*/

i=IFindExtentInList(pw, wExtent, cExtentEntries);
iSave=i;

while (i < cExtentEntries)
pw[i++]=pw[i+1];

cExtentEntries--;

//Save these values back. pw must be decremented first.
*(--pw)=cExtentEntriesMax;
*(--pw)=cExtentEntries;

LocalUnlock(hMem);

if (iSave==0)
{
/*
* Before we change the horizontal extent, we must make sure that
* the origin of the listbox is visible through forcing a scroll.
* If this is not done, and the listbox is scrolled one or
* more pixels to the right, the scrollbar WILL NOT disappear
* even if all remaining strings fit inside the client area
* of the listbox.
*
* This is only done here since this the only case where this
* might happen is when we change the extent.
*/
SendMessage(hList, WM_HSCROLL, SB_TOP, MAKELONG(0, hList));
SendMessage(hList, LB_SETHORIZONTALEXTENT, pw[2], 0L);

return pw[2];
}


return ((WORD)0);
}






/*
* FReAllocExtentList
*
* Purpose:
* Handles reallocation of the list in blocks of +/- CBALLOCUNIT
*
* Parameters:
* fGrow BOOL if TRUE, instructs this function to allocate
* an additional ALLOCUNIT.
* If FALSE, shrinks the memory block by an ALLOCUNIT.
*
* Return Value:
* BOOL TRUE if successfully reallocated. FALSE otherwise.
*
*/

BOOL FReAllocExtentList(HANDLE hMem, BOOL fGrow)
{
WORDwSize;

/*
* Allocate an additional 128 entries. A 256 byte block is a
* decent reallocation size.
*/
wSize=LocalSize((HLOCAL)hMem);
wSize+=(fGrow) ? ((int)CBALLOCUNIT) : (-(int)CBALLOCUNIT);

/*
* This returns FALSE if the realloc was unsuccessful. TRUE
* otherwise because the return handle is !=0
*
*/
return (BOOL)LocalReAlloc(hMem, wSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
}






/*
* WGetListboxStringExtent
*
* Purpose:
* Returns the extent, in pixels, of a string that will be or is
* in a listbox. The hDC of the listbox is used and an extra
* average character width is added to the extent to insure that
* a horizontal scrolling listbox that is based on this extent
* will scroll such that the end of the string is visible.
*
* Parameters:
* hList HWND handle to the listbox.
* psz LPSTR pointer to string in question.
*
* Return Value:
* WORD Extent of the string relative to listbox.
*
*/

WORD WGetListboxStringExtent(HWND hList, LPSTR psz)
{
TEXTMETRIC tm;
HDC hDC;
HFONT hFont;
WORD wExtent;


/*
* Make sure we are using the correct font.
*/
hDC=GetDC(hList);
hFont=(HFONT)SendMessage(hList, WM_GETFONT, 0, 0L);

if (hFont!=NULL)
SelectObject(hDC, hFont);

GetTextMetrics(hDC, &tm);

/*
* Add one average text width to insure that we see the end of the
* string when scrolled horizontally.
*/

//
// changed to GetTextExtentPoint - KoryG - 3/24/92
//
{
SIZE Size;

GetTextExtentPoint(hDC, psz, lstrlen(psz), &Size);
// fudge for tabs!
wExtent=(WORD)Size.cx + (WORD)tm.tmAveCharWidth + 180;
}

ReleaseDC(hList, hDC);

return wExtent;
}






/*
* IFindExtentInList
*
* Purpose:
* Does an binary search on the sorted extent list and returns
* an index to the one that matches. If there is no match,
* the index gives the point where the extent entry should go.
*
* Note that an altered search algorithm is used since the list
* is descending instead of ascending.
*
* Parameters:
* pw WORD * pointer to extent list.
* wExtent WORD extent to find or find an index for.
* cExtentEntries WORD count of entries in list.
*
* Return Value:
* iExtent WORD index into lpw where wExtent exists or where
* it should be inserted.
*
*/

WORD IFindExtentInList(WORD *pw, WORD wExtent, WORD cExtentEntries)
{
int i = 0; //These MUST be signed!
int iPrev;
int iMin;
int iMax;

//Set upper limits on search.
iMin=0;
iMax=cExtentEntries+1;


do
{
iPrev=i;
i=(iMin + iMax) >> 1;

if (i==iPrev)
{
i++;
break;
}

//Change the min and max depending on which way we need to look.
if (wExtent < pw[i]) // < since list is descending. > otherwise
iMin=i;
else
iMax=i;

if (iMax==iMin)
break;
}
while (wExtent != pw[i]);


/*
* When we get here, i is either where wExtent is or where it should
* go--so return it.
*/
return i;
}