From: kurisuto@BACH.UDEL.EDU ("Sean J. Crist")
Subject: More free code: Simplifying the List Manager 
Date: 18 Sep 92 03:33:13 GMT 


     The following code, once again, is nothing particularly glamorous; it
simply makes it easier to use the List Manager to create and manage lists
of strings.  One of the most common uses for the List Manager is
scrollable, one-dimensional, fixed-size
lists of strings (as in SFGetFile, SFPutFile).  The List Manager is good
for creating all kinds of lists (such as lists of icons), but a lot of
this functionality is a hassle for programmers who only need a simple list
of strings.
     The code below allows you create and dispose of lists of strings.  It
allows you to add, rename, and remove elements in the list, and handles
mouse clicks and update events.  It also keeps the lists in alphabetical
order.
     I remember having a lot of trouble learning how to call the List
Manager properly; I hope that this code helps somebody else.

How to use these routines:
     Every one of these routines takes a ListHandle as one of its
parameters.  It is OK to have several lists going at one time; just make
sure you pass the right ListHandle when you call one of these routines.
     
CreateList:  Call this routine to create a new list of strings.  Pass it
an empty ListHandle, as well as the pointer to the window to contain the
list, and the rectangle in which the list should be enclosed.  The list
can take up an entire window, or only
part of it, as you prefer.  The scroll bar will be drawn inside the
rectangle you specify, so you don't need to leave extra room for it. 
Initially, the list will contain no strings.

UpdateList:  Call this in response to an Update event to redraw the
portions of the list which need it.  This routine assumes that you have
already called BeginUpdate for the window containing the list, and that
you have not yet called EndUpdate.

DoClick:  Call this function in response to a MouseDown event inside your
list's rectangle.  DoClick usually returns FALSE, but it returns TRUE if
this click is the second click of a double-click (i.e., TRUE means the
user double-clicked an item).

TurnOffSelection:  This routine unhilights the currently hilighted cell,
if any.

ListSelection:  This returns the string of the currently selected cell. 
If no cell is currently selected, the empty string is returned.

AddCell:  Call this routine to add a new cell to the list.  The string you
pass to this routine is automatically alphabetized within the list.  Bug
alert:  the first element or two of the list are sometimes not in
alphabetical order.  If you take the time
to work out this kink, please let me know what you did (I could figure
this out, but I just haven't taken the time to).  This bug is cosmetic and
does not crash the program.

RenameCell:  Changes the string in a cell to another string, and
realphabetizes the list, if necessary.  

DeleteCell:  Removes the cell with the given string from the list.

DisposList:  Call this routine when you are completely finished with a
list and want to deallocate its memory.

     Credit: The routines CreateList, UpdateList, DoClick, and DisposList
are loosely based on some code I received from somebody of
comp.sys.mac.programmer around two years ago.  I've rewritten these
routines, but credit is due to this contributor, whose
name I cannot remember.

     I have successfully called these routines for lists in regular
windows as well as in modal dialogs.  Please send bug reports, praise,
money, etc. to kurisuto@bach.udel.edu.

unit StringListInterface;

interface

{Create a new list with no elements.}
 procedure CreateList (var TheListHandle: ListHandle; TheWindow:
WindowPtr; TheRect: Rect);

{Update the art of a list.}
 procedure UpdateList (TheListHandle: ListHandle);

{Handle a click in the list rectangle.  If it was a double-click, we will
return TRUE.}
 function DoClick (TheListHandle: ListHandle; TheWhere: Point): Boolean;

{Turn off any hilited item.}
 procedure TurnOffSelection (TheListHandle: ListHandle);

{Return the string currently selected; empty string if no selection.}
 function ListSelection (TheListHandle: ListHandle): string;

{Add a new cell containing the string parameter to the end of the list}
 procedure AddCell (TheListHandle: ListHandle; NewString: str255);

{Change the name of an existing cell}
 procedure RenameCell (TheListHandle: ListHandle; OldString, NewString:
Str255);

{Remove the cell with the given name from the list.}
 procedure DeleteCell (TheListHandle: ListHandle; TheString: string);

{Get rid of the list when we're done with it, cleaning up all the memory.}
 procedure DisposList (TheListHandle: ListHandle);

implementation

 procedure CreateList;
  const
   StandardList = 0;
  var
   ViewRect: Rect;
   DataBounds: Rect;
   CellSize: Point;
   TempInteger: Integer;  {Just to do a little math}
 begin
{Inset the box to make room for the scroll bar.  Also inset it so we've
got room for a border.}
  ViewRect := TheRect;
  InsetRect(ViewRect, 1, 1);
  ViewRect.Right := ViewRect.Right - 15;
{Set the cell size to the size of the cell}
  CellSize.v := TheWindow^.txSize + 3;
  if CellSize.v = 3 then  {If it hasn't been set, then make it 12 point.}
   begin
    TextSize(12);
    CellSize.v := 15;
   end;
  CellSize.h := ViewRect.Right - ViewRect.Left;
{Now adjust the ViewRect to avoid cutting off the last visible cell}
  TempInteger := (ViewRect.Bottom - ViewRect.Top) div CellSize.v;
  ViewRect.Bottom := ViewRect.Top + (TempInteger * CellSize.v);
{Create the new list.}
  SetRect(DataBounds, 0, 0, 1, 0);
  TheListHandle := LNew(ViewRect, DataBounds, CellSize, StandardList,
TheWindow, FALSE, FALSE, FALSE, TRUE);
  UpdateList(TheListHandle);
 end;

{Update the display of a list.}
 procedure UpdateList;
  var
   ViewRect: Rect;
   ListUpdateRgn: RgnHandle;
 begin
  SetPort(TheListHandle^^.Port);
{Get the List manager to update the list.}
  ViewRect := TheListHandle^^.rView;
  LDoDraw(true, TheListHandle);
  ListUpdateRgn := NewRgn;
  RectRgn(ListUpdateRgn, ViewRect);
  LUpdate(ListUpdateRgn, TheListHandle);
{Draw the border}
  InsetRect(ViewRect, -1, -1);
  FrameRect(ViewRect);
{Clean up after ourselves}
  DisposeRgn(ListUpdateRgn);
 end;

{Handle a click in the list rectangle.  If it was a double-click, we will
return TRUE.}
 function DoClick;
 begin
  SetPort(TheListHandle^^.Port);
  LDoDraw(TRUE, TheListHandle);
  DoClick := LClick(TheWhere, 0, TheListHandle);
 end;

{Turn off any hilited item.}
 procedure TurnOffSelection;
  var
   ResultPoint: Point;
 begin
  SetPt(ResultPoint, 0, 0);
  if LGetSelect(TRUE, ResultPoint, TheListHandle) then
   LSetSelect(FALSE, ResultPoint, TheListHandle);
 end;

{Return the string currently selected; empty string if no selection.}
 function ListSelection;
  var
   ResultPoint: Point;
   ResultString: Str255;
   StringPointer: Ptr;
   StringLength: Integer;
 begin
  SetPt(ResultPoint, 0, 0);
  if LGetSelect(TRUE, ResultPoint, TheListHandle) then
{If there is a cell selected, then get the string value of that string. 
There ought to be an}
{easier way to do this than mucking around in the memory like this.  >:-( 
  }
   begin  {If there is a cell selected, then return the string of the cell.}
    StringPointer := Ptr(Ord(@ResultString) + 1);
    StringLength := 255;  {This is the maximum amount of data we are
allowed to move.}
    LGetCell(StringPointer, StringLength, ResultPoint, TheListHandle);
    StringPointer := Ptr(Ord(@ResultString));
    StringPointer^ := StringLength;
    ListSelection := ResultString;
   end
  else  {Otherwise, return the empty string to show that nothing is selected.}
   ListSelection := '';
 end;

{Add a new cell containing the string parameter to the end of the list}
 procedure AddCell;
  var
   Counter: Integer;
   CellPoint: Point;
   OldString: Str255;
   CompResult: Integer;
   StringLength: Integer;
   StringPointer: Ptr;
   done: Boolean;
 begin
{Step 1:  Circle through the loop and figure out where we should insert
the new}
{cell.  We do this to put the list in alphabetical order, and to keep it
that way as}
{new items are added.}
  CellPoint.h := 0;
  CellPoint.v := 0;
  Done := false;
  while not done do
   begin
    if LNextCell(TRUE, TRUE, CellPoint, TheListHandle) then
     begin
      StringPointer := Ptr(Ord(@OldString) + 1);
      StringLength := 255;  {This is the maximum amount of data we are
allowed to move.}
      LGetCell(StringPointer, StringLength, CellPoint, TheListHandle);
      StringPointer := Ptr(Ord(@OldString));
      StringPointer^ := StringLength;
{Now, compare the new string with the contents of the cell and decide
whether the new string}
{should come before or after this cell.}
      CompResult := RelString(NewString, OldString, false, true);
      case CompResult of
       sortsBefore, sortsEqual: 
       done := true;
       SortsAfter: 
       ;
      end;
     end
    else
{There are no more rows, so that's all.}
     begin
      done := true;
     end;
   end;

{Add the new row at the top of the list.}
  CellPoint.v := LAddRow(1, CellPoint.v, TheListHandle);
{Put the string into the cell.  Once again, there ought to be an easier
way to do this.}
  LSetCell(Pointer(Ord(@NewString) + 1), Length(NewString), CellPoint,
TheListHandle);
 end;


 procedure RenameCell;
  var
   CellPoint: Point;
   DataPtr: Ptr;
   DataLen: Integer;
 begin
  SetPt(CellPoint, 0, 0);
  DataPtr := Pointer(Ord(@OldString) + 1);
  dataLen := Length(OldString);
  if LSearch(dataPtr, dataLen, nil, CellPoint, TheListHandle) then
   begin
    DataPtr := Pointer(Ord(@NewString) + 1);
    dataLen := Length(NewString);
    LSetCell(DataPtr, dataLen, CellPoint, TheListHandle);
   end
  else
   begin
{The programmer asked us to rename a cell which doesn't exist.  We'll just
beep angrily}
{three times.  It's the programmer's responsibility to see that the cell
in question actually}
{does exist before calling this routine.}
    Sysbeep(1);
    Sysbeep(1);
    Sysbeep(1);
   end;
 end;


{Remove the cell with the given name from the list.}
 procedure DeleteCell;
  var
   CellPoint: Point;
   DataPtr: Ptr;
   DataLen: Integer;
 begin
  SetPt(CellPoint, 0, 0);
  DataPtr := Pointer(Ord(@TheString) + 1);
  dataLen := Length(TheString);
  if LSearch(dataPtr, dataLen, nil, CellPoint, TheListHandle) then
   begin
    LDelRow(1, CellPoint.v, TheListHandle);
   end
  else
   begin
{The programmer asked us to delete a cell which doesn't exist.  We'll just
beep angrily}
{three times.  It's the programmer's responsibility to see that the cell
in question actually}
{does exist before calling this routine.}
    Sysbeep(1);
    Sysbeep(1);
    Sysbeep(1);
   end;
 end;

{Get rid of the list when we're done with it, cleaning up all the memory.}
 procedure DisposList;
 begin
  LDispose(TheListHandle);
 end;


end.