                                                                     Customizing
                                                                          Linked
                                                                           Lists





         "As we acquire knowledge, things do not become more comprehensible, but
         more mysterious."

                                                                     Will Durant





         Doubly-linked lists are ideal for managing large lists of data, they
         are memory efficient and fast. The only problem is they are compli-
         cated! Fortunately, the Toolkit provides a very easy to use doubly-
         linked list object called DLLOBJ. DLLOBJ is an abstract object designed
         specifically to simplify the task of extending the object. In this
         chapter, techniques for developing a custom doubly-linked list for
         managing records is discussed.

         You might want to consider re-reading the section on linked list theory
         (page 9-1) before proceeding.



DLLOBJ

         Unlike many objects, you do not need to understand very much about
         DLLOBJ to create descendant objects. All the following methods (which
         were described in chapter 9) are unaffected by the type of data stored
         in the list, and do not need to be modified in descendant objects:

         procedure   Advance(Amount:longint);
         procedure   Retreat(Amount:longint);
         function    NodePtr(NodeNumber:longint): DLLNodePtr;
         procedure   Jump(NodeNumber:longint);
         procedure   ShiftActiveNode(NewNode: DLLNodePtr; NodeNumber: longint);
         procedure   DelNode(Node:DLLNodePtr);
         procedure   DelAllStatus(BitPos:byte;On:boolean);
         function    TotalNodes: longint;
         function    ActiveNodeNumber: longint;
         function    ActiveNodePtr: DLLNodePtr;
         function    StartNodePtr: DLLNodePtr;
         function    EndNodePtr: DLLNodePtr;
         procedure   EmptyList;
         procedure   Sort(SortID:shortint;Ascending:boolean);
         procedure   SwapNodes(Node1,Node2:DLLNodePtr);


         The DLLOBJ stores un-typed data in binary format. You can literally
         stored any type of data in a DLLOBJ list. The following methods add and
         modify data in a list:


19-2                                                       Extending the Toolkit

--------------------------------------------------------------------------------

         function Add(var TheData;Size:longint): integer;
         function Change(Node:DLLNodePtr;var TheData;Size:longint): integer;
         function InsertBefore(Node:DLLNodePtr;var TheData;Size:longint): inte-
         ger;


         Each of these three methods are passed an untyped parameter and a lon-
         gint indicating the size of the data. In descendant objects, you will
         call these methods to manipulate the data in the list. The following
         methods return information about the data stored in the list:

         procedure   Get(var TheData);
         procedure   GetNodeData(Node:DLLNodePtr;Var TheData);
         function    GetNodeDataSize(Node:DLLNodePtr):longint;
         function    GetMaxNodeSize: longint;


         The most used method is GetNodeData, which will update a passed un-
         typed parameter with the data stored in the list. It is the fundamental
         way for a descendant object to get data from the list.



Extending DLLOBJ

         The main reason for extending DLLOBJ is to make the new object manage a
         specific type of data. The Toolkit includes the descendant StrDLLOBJ,
         which is specifically designed to store strings, and FileDLLOBJ, which
         stores DOS file details.

         In this section, DLLOBJ will be extended and customized to store a
         record. To illustrate the principles involved, we will create a new
         object RecordDLLOBJ to store the following record data:

         RecordInfo = record
            FirstName: string[15];
            LastName: string[15];
            Company: string[20];
            Tel: string[10];
            CumDollarsSpent: real;
            LastOrder: longint;
            Comments: string[40];
         end;


         The main methods that need to be customized are the data manipulation
         methods, i.e. Add, Change and InsertBefore. If you want to display the
         object in a Browse or List window you must also customize the GetStr
         virtual method. GetStr is called by the browse and list objects, and is
         simply a function which returns the data stored at a node in string
         form. The fifth method which usually needs to be customized is Wron-



Customizing Linked Lists                                                    19-3

--------------------------------------------------------------------------------

         gOrder. This method provides the Sort method with the information
         needed to sort the data, and is discussed in a later section. The new
         object would therefore be declared as follows:

         RecordListOBJ = object (DLLOBJ)
            constructor Init;
            function    Add(Rec:RecordInfo): integer;
            function    Change(Node:DLLNodePtr;Rec:RecordInfo): integer;
            function    InsertBefore(Node:DLLNodePtr;Rec:RecordInfo): integer;
            function    WrongOrder(Node1,Node2:DLLNodePtr;
                                   Asc:boolean): boolean;        VIRTUAL;
            function    GetStr(Node:DLLNodePtr;
                               Start,Finish: longint):string;    VIRTUAL;
            destructor  Done;                                    VIRTUAL;
         end; {RecordListOBJ}


         Notice that Add, Change and InsertBefore are each passed a variable of
         type RecordInfo. All these methods need to do is call their correspond-
         ing DLLOBJ method and pass the record as an untyped parameter together
         with the record size. The three methods would be implemented as
         follows:

         function RecordDLLOBJ.Add(Rec:RecordInfo): integer;
         begin
            Add := DLLOBJ.Add(Rec,sizeof(Rec));
         end; {RecordDLLOBJ.Add}

         function RecordDLLOBJ.Change(Node:DLLNodePtr;
                                       Rec:RecordInfo): integer;
         begin
            Change := DLLOBJ.Change(Node,Rec,sizeof(Rec));
         end; {RecordDLLOBJ.Change}

         function RecordDLLOBJ.InsertBefore(Node:DLLNodePtr;
                                             Rec:RecordInfo): integer;
         {}
         begin
            InsertBefore := DLLOBJ.InsertBefore(Node,Rec,sizeof(Rec));
         end; {RecordDLLOBJ.InsertBefore}


         It's really as simple as that.

         The function method GetStr is passed three parameters; a node pointer
         indicating which data to access, and the Start and Finish parameters of
         type longint. Start and Finish identify the first and last character
         positions of the sub-string to be returned by the function. The DLLOBJ
         method GetNodeData can be used to retrieve the node data, and then the
         data must be converted into string form. The requested portion of this
         string can then be returned. GetStr could be implemented as follows:



19-4                                                       Extending the Toolkit

--------------------------------------------------------------------------------

         function RecordDLLOBJ.GetStr(Node:DLLNodePtr;Start,Finish: lon-
         gint):string;
         {Returns string representation of record}
         var
           Temp: string;
           Rec: RecordInfo;
         begin
            if Node = nil then
               GetStr := 'Not found'
            else
            begin
               GetNodeData(Node,Rec);  {inherited method}
               with Rec do
               begin
                  Temp := inttostr(ActiveNodeNumber)+': '+
                          FirstName+
                          LastName+
                          Company;
                  if Finish > 53 then
                     Temp := Temp + PicFormat(Tel,'(###) ###-####',' ')+' ';
                  if Finish > 68 then
                     Temp := Temp + JultoStr(LastOrder,MMDDYY)+' ';
                  if Finish > 77 then
                     Temp := Temp + FmtNumberTOT.FormattedReal
                                    (CumDollarsSpent,2,10)+' ';
                  if Finish > 88 then
                     Temp := Temp + Comments;
                end;
                GetStr := copy(Temp,Start,succ(Finish-start));
             end;
         end; {RecordDLLOBJ.GetStr}


         GetStr will never be called with Start and Finish parameters that are
         more than 255 characters apart. In this case example, the entire record
         can be represented by a string, so GetStr builds a string and returns
         the requested sub-string. In cases where the record is larger than will
         fit in a 255 string, the method should only convert the requested por-
         tion of the record in string form. Note that the Browse object calls
         GetStr many times during a browse session, and that GetStr needs to
         respond quickly to avoid sluggishness. If you find that browsing is too
         slow, try the program with range checking, stack checking etc. turned
         off. These compiler directives slow down string-related activity con-
         siderably.


         The file EXTLINK.PAS contains the entire code for the customized
         RecordDLLOBJ object, and is only about 100 lines long. Use this unit as
         a template for your own specific record types.




Customizing Linked Lists                                                    19-5

--------------------------------------------------------------------------------

Displaying RecordListOBJ Records

         The List and Browse objects automatically support any descendant of
         DLLOBJ, and so the new object RecordDLLOBJ can be easily displayed in a
         List or Browse window.

         The key method controlling what information is displayed in the window
         is the GetStr method. Thanks to polymorphism, the List and Browse
         objects don't need to know the specifics of GetStr. When they need a
         node in string form, they simply call the DLLOBJ (or descendant) GetStr
         method, and use whichever string is returned.

         Listed below is the demo program, EXTDEM5.PAS, which displays the list
         contents in a browse window. Figure 19.1 shows the resultant output.

         Program ExtendedDemoFive;

         Uses DOS,CRT,
              totFAST, totINPUT, totList, extLINK, totSTR;

         var
           RecList: RecordDLLOBJ;
           ListWin: BrowseLinkOBJ;

         procedure BuildTheList(Filename:string);
         {loads in the data from disk - could also be from d/b}
         var
           F: file of RecordInfo;
           Rec: RecordInfo;
           Ecode: integer;
         begin
            assign(F,filename);
            {$I-}
            reset(F);
            {$I+}
            if ioresult <> 0 then
            begin
               writeln('The file ',filename,' cannot be located.');
               writeln('Demo aborting');
               halt(1);
            end;
            Ecode := 0;
            RecList.Init;
            while not eof(F) and (Ecode = 0) do
            begin
               Read(F,Rec);
               with Rec do
               begin
                  FirstName:= padleft(FirstName,15,' ');
                  LastName:= padleft(LastName,15,' ');
                  Company:= padleft(Company,20,' ');
               end;



19-6                                                       Extending the Toolkit

--------------------------------------------------------------------------------

               Ecode := RecList.Add(Rec);
            end;
            close(F);
         end; {BuildtheList}

         begin {Main program}
            BuildTheList('EXTDEM5.DBF');
            Screen.Clear(white,''); {paint the screen}
            Key.SetFast;
            with ListWin do
            begin
               Init;
               AssignList(RecList);
               Go;
            end;
         end.


Figure 19.1                                                             [SCREEN]
Browsing
RecordDLLOBJ



         As it stands, the string returned by GetStr is not really suited for
         displaying in a list. Each item in the list would be wider than the
         list display! A quick solution is to build a descendant of RecordDLLStr
         and replace the GetStr method with a method which only returns a short
         string, e.g. last name. The on-disk example EXTDEM6.PAS illustrates
         this technique. In this example, ListLinkOBJ is also extended and cus-
         tomized to show the full record in the message box at the bottom of the
         list. This technique was described on page 9-30. Figure 19.2 shows the
         output generated by EXTDEM6.



Figure 19.2                                                             [SCREEN]
Listing
RecordDLLOBJ



Sorting

         It is very easy to make your custom linked lists sortable. To make a
         list sortable, the inherited virtual method WrongOrder must be com-
         pleted. Behind the scenes, the Toolkit sort routines repeatedly call
         WrongOrder to determine whether two nodes are in the correct order.

         The WrongOrder method is declared as follows:

            function WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean): boolean;




Customizing Linked Lists                                                    19-7

--------------------------------------------------------------------------------

         WrongOrder is a boolean function method which should return TRUE if the
         data in the two nodes are in the wrong order. The method is passed
         three parameters; two DLLNodePtr pointers to identify the nodes to be
         compared, and a boolean to indicate whether the list is being sorted in
         ascending (true) or descending (false) order.

         The WrongOrder method must therefore get the data from the two nodes,
         and decide if they are in the appropriate order for the sort. All
         DLLOBJ objects include a shortint vSortID. This variable can be used to
         provide multiple sorting capabilities. When you call the Sort method,
         you must pass two parameters; a Sort ID, and a boolean to indicate
         whether the sort order is ascending or descending. The Toolkit automat-
         ically updates vSortID with the parameter passed to Sort. WrongOrder
         should therefore check vSortID to determine what data to use in
         deciding whether the order is correct.

         In the RecordDLLOBJ example, the following codes might be appropriate:

                   1      sort on LastName
                   2      sort on Company
                   3      sort on Tel
                   4      sort on CumDollarsSpent
                   5      sort on LastOrder

         The actual codes you select are not important. You decide which fields
         you want to be able to sort on, and which codes to use. Using the
         listed codes, the WrongOrder method for the RecordDLLOBJ object would
         be as follows:

         function RecordDLLOBJ.WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean):
         boolean;
         var
           S1,S2: string;
           Rec1,Rec2: RecordInfo;
           R1,R2: real;
           D1,D2: longint;
         begin
            GetNodeData(Node1,Rec1);
            GetNodeData(Node2,Rec2);
            if vSortID in [1,2,3] then
            begin
               case vSortID of
                  1:begin      {LastName}
                     S1 := Rec1.LastName;
                     S2 := Rec2.LastName;
                  end;
                  2: begin     {Company}
                     S1 := Rec1.Company;
                     S2 := Rec2.Company;
                  end;
                  3: begin     {Tel}



19-8                                                       Extending the Toolkit

--------------------------------------------------------------------------------

                     S1 := Rec1.Tel;
                     S2 := Rec2.Tel;
                  end;
               end; {case}
               if Asc then
                  WrongOrder := (S1 > S2)
               else
                  WrongOrder := (S2 > S1);
            end
            else if vSortID = 4 then  {CumDollars}
            begin
               R1 := Rec1.CumDollarsSpent;
               R2 := Rec2.CumDollarsSpent;
               if Asc then
                  WrongOrder := (R1 > R2)
               else
                  WrongOrder := (R2 > R1);
            end
            else                       {LastOrder}
            begin
               D1 := Rec1.LastOrder;
               D2 := Rec2.LastOrder;
               if Asc then
                  WrongOrder := (D1 > D2)
               else
                  WrongOrder := (D2 > D1);
            end;
         end; {RecordDLLOBJ.WrongOrder}


         To sort the list, call the method Sort, e.g. MyList.Sort(1,true). The
         demo file EXTDEM6.PAS includes a sort statement.



Using Status Codes

         List status codes are used internally by the Toolkit to support the
         displaying of lists in windows. In some circumstances, you may be able
         to use this facility in your custom lists.

         All items stored in a DLLOBJ, or descendant object, include a Status
         byte. Each of the eight bits in the Status byte can be used for a
         different purpose. Chapter 9: Managing Lists (page 9-27), describes how
         the first two bits are used by the List display object. You may recall
         that bit 0 is used to identify whether the item is tagged, and bit 1
         identifies which items to display in an alternate color. The remaining
         six bits can be used for your own custom needs. For example, you might
         use bit 2 to identify all customers with a delinquent account, and bit
         3 might identify whether the customer is international or domestic, or




Customizing Linked Lists                                                    19-9

--------------------------------------------------------------------------------

         you might extend ListOBJ to use a three-color list, and use bit 2 to
         identify items to show in the third color. Whatever the reason, they
         are there if you need them.

         To access the Status bits, you need to know how data is stored at each
         node. Each node in a list actually points to a DLLNodeOBJ. This object
         manages the storage of data at the node, as well as the node status
         byte. The following four DLLNodeOBJ methods support the Status byte:

                  GetStatus(Bitpos:byte): boolean;
                  SetStatus(BitPos:byte;On:boolean);
                  GetStatusByte:byte;
                  SetStatusByte(Val:byte);

         To call these methods use the syntax MyList.NodePtr(number)^.method.
         Use the Set methods to change a status bit, and the get methods to
         check the current status. For example, to set the fourth bit to true
         for the 12th entry in the list MyList, you would use the following
         statement:

                  MyList.NodePtr(12).SetStatus(3,true);
