Expanded Lists
Volume Number: | | 9
|
Issue Number: | | 8
|
Column Tag: | | C Workshop
|
Related Info: List Manager
Expanding the List Manager
Adding graphics to lists with Custom LDEFs
By Mark W. Batten, Washington, DC
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
The List Manager is not especially well-named. The term list suggests one column of text, as in a shopping list or an address list. In fact, the List Manager is much more flexible: it can maintain information in rows and columns, as in a spreadsheet, and can manage data of any kind, including graphics.
The purpose of this article is to demonstrate two simple list definition procedures, or LDEFs, that extend the List Managers text-only list to include graphics. First, well create a list of icons with text underneath, sort of like those that appear in Finder windows. Second, well develop a list that puts a small icon next to each entry, the way the standard Open and Save dialog boxes do.
Setting up the application for our LDEFs
When using a custom LDEF, the List Manager leaves the drawing of each cell -- and the interpretation of the data in the cell -- entirely up to you. So to create a list that has both text and graphics in a list, all you have to do is put the graphics data and text in each cell. The LDEF will then interpret the data when it draws the cell. In our example, we just have the application stuff the icon data into the cell, followed by the text (a Pascal string), using List Manager routines:
/* 1 */
LSetCell(IconPtr,128,theCell,LH);
LAddToCell(IconName,(int)IconName[0]+1,theCell,LH);
Example 1: The Icon List
The List Manager sends four different kinds of messages to LDEFs: initialize, draw, highlight, and close. The initialize and close routines are rarely needed unless your LDEF needs to allocate its own memory for storage; well focus here on the draw and highlight routines to show how they interpret the icon and text data.
Our first example is a list of icons with text underneath. Such a list has lots of uses: showing the user the files in an individual directory, or in an archive file; asking the user to choose from among a list of options; or any time you want to present a list of items in a graphically appealing way. Lets examine the DoDraw routine in detail.
/* 2 */
doDraw(select,r,offset,LH)
Boolean select;
Rect *r;
int offset;
ListHandle LH;
{
The List Manager sends an LDrawMsg for each cell in the list, so the DoDraw routines task is to draw one cell. The cell to draw is passed to the LDEF in two parameters: the Rect r is the rectangle in which to draw the cell, and offset holds the offset into the lists cell data where the information for this cell begins.
The routine begins with a number of cosmetic effects: it fills the cell with a gray pattern, and then draws a border around the icon. These obviously arent essential, but are included here because they help the icon stand out against the background and to give it a three-dimensional appearance:
/* 3 */
FillRect(r,grey);
r->top+=(r->bottom-r->top-32)/2;
r.left+=(r->right-r->left-32)/2;
r->bottom=r->top+32;
r->right=r->left+32;
InsetRect(r,-4,-4);
r1=*r;
FillRect(r,wite);
FrameRect(r);
InsetRect(r,3,3);
FrameRect(r);
InsetRect(r,1,1);
PenPat(dark);
for(i=1;i<3;i++){
MoveTo(r->left-(i+1),r->bottom+i);
LineTo(r->right+i,r->bottom+i);
MoveTo(r->right+i,r->top-(i+1));
LineTo(r->right+i,r->bottom+i);
}
PenNormal();
Now we draw the icon. To do so, we have to set up a BitMap that we can pass to CopyBits. Because each cell begins with icon data (which the application put there, remember?), the BitMaps baseAddr should point to the beginning of the cells data. The other BitMap fields are easy, since theyre determined by the size of an icon:
/* 4 */
HLock((**LH).cells);
icon.baseAddr=(char *)(*(**LH).cells)+offset;
SetRect(&icon.bounds,0,0,32,32);
icon.rowBytes=4;
Now were ready to draw the icon and move on to the text, which the application stored in the cell right after the 128 bytes of icon data. Note that the destination BitMap is supplied by the ListRec, which maintains a GrafPtr for the port the list appears in:
/* 5 */
src=icon.bounds;
CopyBits(&icon,&((**LH).port->portBits),&src,r,0,0L);
offset+=128;
Now were ready to draw the text under the icon. First, lets copy the text out of the cell data into a local string variable:
/* 6 */
q=(char *)(*(**LH).cells)+offset;
s=p;
len=*s++=*q++;
while(--len>=0)*s++=*q++;
HUnlock((**LH).cells);
Now we want to center the text in the cell. If the text is too long, though, we dont want it to run off the edges, so we curtail the string and add a character to the end. In this example, the LDEF limits text to a maximum of five characters. We also need to erase the texts rectangle before drawing it, because the text wont be readable if we just draw the string over the gray pattern we painted into the cell:
/* 7 */
if(*p>5){
p[5]=0xC9; /* ' ' */
*p=5;
}
c=(r->left+16)-(StringWidth(p)/2);
GetFontInfo(&inf);
i=r->bottom+inf.ascent+inf.descent+inf.leading;
SetRect(&r1,c,r->bottom+4,c+StringWidth(p),i);
EraseRect(&r1);
MoveTo(c,i);
DrawString(p);
Thats all there is to it; the final lines just invert the icon if the cell is supposed to be highlighted, first clearing the high bit of the global variable HiliteMode to ensure that the highlighting will use the highlight color on color machines:
/* 8 */
if(select){
HiliteMode&=127;
InvertRect(r);
}
Example 2: Open and Save Lists
Our second example is the kind of list seen in the Macs Open and Save dialog boxes: a list of text items with a small icon (SICN) to the left of each entry. This type of list could be handled in much the same way as Example 1, but lets experiment with a slightly different technique that uses a little less memory.
For our example, we indicate a files type with one of three SICNs: a generic application icon for applications, a folder icon for folders, and a generic document icon for all others. With so few icons, theres no need to store the actual data in each individual cell. Instead, as part of the setup, we have the application read in the appropriate SICN resource and store it in the ListHandles userHandle field:
/* 9 */
SmIcons=Get1Resource('SICN',128);
(**(LH)).userHandle=SmIcons;
Then, in storing data in the cells, the application puts in the text, followed by one byte that indicates which SICN in the list to use:
/* 10 */
PBGetCatInfo(&theParamBlk,false); /* get a file's info */
LDoDraw(false,LH);
b=(**(LH)).dataBounds.bottom;
theCell.v=LAddRow(1,++b,LH);
theCell.h=0;
if((theParamBlk.ioFlAttrib & 16)!=0)theType=0;
else{
if(theParamBlk.ioFlFndrInfo.fdType=='APPL')theType=2;
else theType=1;
}
LSetCell(n,(int)(*n+1),theCell,LH);
LAddToCell(&theType,1,theCell,LH);
LDoDraw(true,LH);
Now lets look at the Example 2 LDEFs DoDraw routine to see how to interpret this data. Here we begin by erasing the cell, which is more appropriate for this list than the gray pattern we used in Example 1. Then we set up the destination rectangle to the left edge of the cell, and set up the bounds and rowBytes of the source BitMap:
/* 11 */
dst=*r;
dst.right=dst.left+16;
SetRect(&icon.bounds,0,0,16,16);
icon.rowBytes=2;
To set up the baseAddr field, we first retrieve the byte that we stored at the end of the text. Because there are 32 bytes in each small icon, we multiply the flag byte by 32 to get the proper position in the small icon list, stored in the userHandle. A call to CopyBits completes the drawing:
/* 12 */
HLock((**LH).cells);
s=q=(char *)*(**LH).cells;
q+=off;
s+=off;
q+=(*q+1);
icnoff=*q<<5;
HLock((**LH).userHandle);
icon.baseAddr=(*(**LH).userHandle)+icnoff;
CopyBits(&icon,&((**LH).port->portBits),&(icon.bounds), &dst,0,0L);
HUnlock((**LH).userHandle);
Drawing the text is then similar to Example 1, except that we move the pen to the
right of the small icon and center it in the cell:
/* 13 */
GetFontInfo(&inf);
len=inf.ascent+inf.descent+inf.leading;
MoveTo(dst.right+2,(r->bottom+r->top+len)/2-inf.descent);
DrawString(s);
HUnlock((**LH).cells);
if(select)InvertRect(r);
This example uses black-and-white icons, but color icons could be handled the same way; just use a PixMap instead of a BitMap.
Example 1 Source Code
#include "SetUpA4.h"
Pattern grey={ 170,85,170,85,170,85,170,85 },
wite={ 0,0,0,0,0,0,0,0 },
dark={ 119,238,221,187,119,238,221,187 };
pascal void main(message,select,r,theCell,offset,len,LH)
int message,offset,len;
ListHandle LH;
Cell theCell;
Boolean select;
Rect *r;
{
int l;
RememberA0();
SetUpA4();
switch(message){
case 0: break;
case 1:
case 2: doDraw(select,r,offset,LH); break;
case 3: break;
}
RestoreA4();
}
doDraw(select,r,offset,LH)
Boolean select;
Rect *r;
int offset;
ListHandle LH;
{
BitMap icon;
Rect src,r1;
char p[32],*q,*s,c;
register int len,i;
FontInfo inf;
FillRect(r,grey);
r->top+=(r->bottom-r->top-32)/2;
r->left+=(r->right-r->left-32)/2;
r->bottom=r->top+32;
r->right=r->left+32;
InsetRect(r,-4,-4);
r1=*r;
FillRect(r,wite);
FrameRect(r);
InsetRect(r,3,3);
FrameRect(r);
InsetRect(r,1,1);
PenPat(dark);
for(i=1;i<3;i++){
MoveTo(r->left-(i+1),r->bottom+i);
LineTo(r->right+i,r->bottom+i);
MoveTo(r->right+i,r->top-(i+1));
LineTo(r->right+i,r->bottom+i);
}
PenNormal();
HLock((**LH).cells);
icon.baseAddr=(char *)(*(**LH).cells)+offset;
SetRect(&icon.bounds,0,0,32,32);
icon.rowBytes=4;
src=icon.bounds;
CopyBits(&icon,&((**LH).port->portBits),&src,r,0,0L);
offset+=128;
q=(char *)(*(**LH).cells)+offset;
s=p;
len=*s++=*q++;
while(--len>=0)*s++=*q++;
HUnlock((**LH).cells);
if(*p>5){
p[5]=0xC9;
*p=5;
}
c=(r->left+16)-(StringWidth(p)/2);
GetFontInfo(&inf);
MoveTo(c,r->bottom+inf.ascent+inf.descent+inf.leading);
DrawString(p);
if(select){
HiliteMode&=127;
InvertRect(r);
}
}
Example 2 Source Code
#include "SetUpA4.h"
Pattern grey={ 170,85,170,85,170,85,170,85 };
pascal void main(message,select,r,theCell,offset,len,LH)
int message,offset,len;
ListHandle LH;
Cell theCell;
Boolean select;
Rect *r;
{
/* This routine is the same as in Example 1 */
}
doDraw(select,r,offset,LH)
Boolean select;
Rect *r;
int offset;
ListHandle LH;
{
BitMap icon;
Rect dst;
char *q,*s,icnoff;
int len;
FontInfo inf;
EraseRect(r);
dst=*r;
dst.right=dst.left+16;
SetRect(&icon.bounds,0,0,16,16);
icon.rowBytes=2;
HLock((**LH).cells);
s=q=(char *)*(**LH).cells;
q+=off;
s+=off;
q+=(*q+1);
icnoff=*q<<5;
HLock((**LH).userHandle);
icon.baseAddr=(*(**LH).userHandle)+icnoff;
CopyBits(&icon,&((**LH).port->portBits),&(icon.bounds),
&dst,0,0L);
HUnlock((**LH).userHandle);
GetFontInfo(&inf);
len=inf.ascent+inf.descent+inf.leading;
MoveTo(dst.right+2,(r->bottom+r->top+len)/2-inf.descent);
DrawString(s);
HUnlock((**LH).cells);
if(select){
HiliteMode&=127;
InvertRect(r);
}
}