                                                                        Managing
                                                                           Lists




         "Three questions are essential... What is the author's object? How far
         has he accomplished it? How far is that object worthy of approbation?

                                                              Nathanial P Willis




Introduction

         Lists, lists, lists. Our lives and our software are often dominated by
         lists. The Toolkit provides three powerful units to help you manage
         software lists. (Your lives are your own responsibility!)

         In this chapter, the totLINK and totLIST units are discussed in detail.
         totLINK provides objects to help you maintain and sort large lists, and
         totLIST provides objects for displaying lists and arrays in windows.

         A common form of software list is a file directory list. The Toolkit
         provides two flexible directory displaying objects, and these are dis-
         cussed in the next chapter.



Array and Linked List Theory

         One-dimensional arrays provide a very quick and convenient way of
         managing lists. An array is a data structure designed to store data
         about a fixed number of identical elements. For example, the following
         variable could be used to store the address of my two sisters' and
         three brothers' addresses:

                  Addresses: array [1..5] of string;

         Data in the array is accessed by specifying the element number. For
         example, the following statement would update my second sister's
         address:

                  Addresses[2] := "38 The Ridgeway, Friern Barnet, London"


         Arrays are very easy to create, but suffer from two major weaknesses:

         q     The size of an array is fixed at compile time. In other words, in
               your source code you must explicitly state the size (or bounds)
               of the array. In our simple example, this was not a problem
               because I have only five siblings. Imagine, however, that you
               need to write a generic name and address program. You don't know
               how many names and addresses will be required, but you must spec-
               ify the number in your program. The typical solution is to choose
               a large number of elements (e.g. 200) and hope that the user
               doesn't need more. Not only does this set a limit on the pro-
               gram's capacity, it also wastes memory if the maximum number of

9-2                                                                 User's Guide

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

               elements are not used. Turbo Pascal automatically allocates the
               memory for the entire array regardless of how many elements are
               actually used. Which leads us to the second shortcoming.

         q     The maximum size of a program's data segment in Turbo Pascal is
               64k. This means that the sum of all the program's global vari-
               ables cannot exceed 64k, and, therefore, no individual data
               structure can exceed 64k. In our example, each element of the
               array consumed 256 bytes, so 256 of these elements would consume
               the entire data segment leaving no room for other global vari-
               ables.



         Linked lists do not suffer from these shortcomings. With a linked list,
         each element is stored independently. The first element in the list
         includes a pointer indicating where the second element in the list is
         stored. The second element in the list includes a pointer to the loca-
         tion of the third element in the list, and so on. When you reach an
         element in the list that points to NIL, you know you have reached the
         end of the list. You do not need to fix the maximum number of elements
         in the list at compile time. Furthermore, the data is stored on the
         heap (not in the data segment) and so is not limited to 64k. The list
         can use all of the available memory.

         Although linked lists are very flexible, they can prove complicated to
         program and manage. Fortunately, the Toolkit provides a very powerful
         object which makes managing linked lists a breeze.



Managing Linked Lists

         The totLINK unit includes the object DLLOBJ. This object provides a
         variety of methods for creating and managing linked lists. If you are
         interested, DLLOBJ actually maintains a doubly-linked list. Each ele-
         ment in the list points to the next element and the previous element.
         This is more efficient when you need to move up and down the list.

         The DLLOBJ object is designed to store raw data, i.e. untyped vari-
         ables. This allows you to store literally any data in a DLLOBJ object.
         It is important to note that DLLOBJ is an abstract object and you
         should not create any instances of type DLLOBJ.

         In this chapter, we will concentrate on a descendant object, StrDLLOBJ,
         designed specifically to manage lists of strings. If you want to create
         objects which manage other data types, all you have to do is create an
         object descended from DLLOBJ. In part 2: Extending the Toolkit, there
         is a chapter describing how to create other descendants of DLLOBJ to
         manage specific data types. By way of an example, it describes how to
         create a linked list object for managing records.



Managing Lists                                                               9-3

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

         Before exploring the StrDLLOBJ methods, you need to understand some
         doubly-linked-list jargon. Each element in the list is referred to as a
         node. Behind the scenes, the Toolkit keeps track of the location of the
         first node in the list, the last node in the list, and the node that
         was last accessed. The last-accessed node is referred to as the active
         node.



Creating a List

         To create a linked list, you must declare an instance of type StrDLLOBJ
         and then call the Init and Add methods. The syntax of these methods is
         as follows:


         Init;

         This method initializes the object and must be called before the other
         methods.


         Add(Str:string): integer;

         This function method creates a new node in the linked list and stores
         it in the string Str. The node is actually created immediately follow-
         ing the active node. Once the node has been added, the active node is
         moved to point to it. The function returns an integer code - a 0
         indicates success, a 1 indicates there was insufficient memory to
         create the node, and a 2 indicates there was enough memory to create
         the node, but too little memory to store the string.


         Ordinarily, to populate a list, all you have to do is call Init, and
         then make multiple calls to Add. Listed below is an extract of the demo
         program, DEMLK1.PAS, which stores a list of names in a linked list.

         var
           GirlFriends: StrDLLOBJ;
           Retcode: integer;

         begin
            with GirlFriends do
            begin
               Init;
               Retcode := Add('Erica');
               if Retcode <> 0 then exit;
               Retcode := Add('Theresa');
               if Retcode <> 0 then exit;
               Retcode := Add('Lynn');
               if Retcode <> 0 then exit;
               Retcode := Add('Donna');
               if Retcode <> 0 then exit;




9-4                                                                 User's Guide

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

               Retcode := Add('Godzilla');
               if Retcode <> 0 then exit;
               {....}
            end;
         end.


         To save checking the value of Retcode every time you add a node, you
         might check that there is enough memory available for all the nodes,
         and ignore the return code, as follows:

         var
            GirlFriends: StrDLLOBJ;
            Retcode: integer;

         begin
            with GirlFriends do
            begin
               Init;
               if maxavail > 200 then
               begin
                  Retcode := Add('Erica');
                  Retcode := Add('Theresa');
                  Retcode := Add('Lynn');
                  Retcode := Add('Donna');
                  Retcode := Add('Godzilla');
                  {...}
               end;
            end;
         end.


         In fact, if you are using Turbo Pascal 6.0 and the extended compiler
         directive {$X+} has been set, you can ignore the function return value,
         as follows:

         {$X+}
         var
            GirlFriends: StrDLLOBJ;

         begin
            with GirlFriends do
            begin
               Init;
               if maxavail > 200 then
               begin
                  Add('Erica');
                  Add('Theresa');
                  Add('Lynn');
                  Add('Donna');
                  Add('Godzilla');



Managing Lists                                                               9-5

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

                  {...}
               end;
            end;
         end.


Maintaining a List

         Once you have created a list, the following methods can be used to
         navigate the list:


         TotalNodes: longint;

         This function method returns a longint identifying the total number of
         nodes in the list.


         ActiveNodeNumber: longint;

         Returns the position of the active node.


         Jump(NodeNumber: longint)

         This method moves the ActiveNodePtr to the specified node number. Add
         inserts a node after the active node pointer. So, to insert a node
         anywhere in the list, call the method Jump to move the active node
         pointer and then call Add.


         Advance(Amount: longint);

         Moves the active node down the list the specified number of nodes. If
         there are insufficient nodes, the active node pointer is moved to the
         end of the list.


         Retreat(Amount: longint);

         Moves the active node up the list the specified number of nodes. If
         there are too few nodes, the active node pointer is moved to the begin-
         ning of the list.




Advanced List Management

         In many programs, all you'll need to do is create a list, add items to
         the list, and then display the list using one of the browse or list
         displaying objects (discussed later). However, if you want to delete
         items from the list, swap the positions of two items in the list, or
         change an item in the list, you will need to access the nodes in the
         list directly.


9-6                                                                 User's Guide

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

         Each node in the list is actually a DLLNodeOBJ object, and many of the
         list manipulation methods access a pointer to the node known as a
         DLLNodePtr.

         Without worrying about the details, all you need to know is how to
         determine the value of DLLNodePtr for any given node. The following
         four function methods serve this purpose:


         NodePtr(NodeNumber:longint): DLLNodePtr;

         Pass this function a node number, and it will return a pointer to the
         node.


         ActiveNodePtr: DLLNodePtr;

         Returns a pointer to the active node.


         StartNodePtr: DLLNodePtr;

         Returns a pointer to the first node in the list.


         EndNodePtr: DLLNodePtr;

         Returns a pointer to the last node in the list.


         Having established the DLLNodePtr value, the following methods can be
         used to manipulate the node data:


         Change(Node:DLLNodePtr;Str:string):integer;

         Changes the value at the node to Str. This function method returns an
         integer to indicate whether the change was a success: 0 indicates suc-
         cess, 2 indicates insufficient memory for the data, and 3 indicates an
         invalid node pointer.


         DelNode(Node:DLLNodePtr);

         Deletes the node.


         SwapNodes(Node1,Node2:DLLNodePtr);

         Swaps the data assigned to the two nodes.


         InsertBefore(Node:DLLNodePtr; Str:string);




Managing Lists                                                               9-7

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

         Inserts a new node immediately before the specified node. (The Add
         method inserts the data after the active node.) This method provides a
         way of inserting a node at the very beginning of the list. After this
         method has been called, the active node pointer is set to the first
         node in the list.


         GetStr(Node:DLLNodePtr; Start,Finish:longint): string;

         This function returns a sub-string of the data stored at the specified
         node. The parameters Start and Finish indicate the starting and ending
         characters to be extracted in the string. For example, values of 6 and
         10 will return five characters commencing with the character in posi-
         tion 6 of the string. Pass values of zero for the Start and Finish
         parameters, if you want the entire string.


         Sort(SortID: shortint; Ascending: boolean);

         This powerful method rearranges the order of all the nodes in a list,
         i.e. it sorts the list. The sort routine is generic and can be utilized
         by any descendant of the DLLOBJ object. In the case of StrDLLOBJ, the
         first parameter has no meaning, and should be set to one. If you want
         the list sorted in ascending order (i.e. lowest first), pass TRUE as
         the second parameter, otherwise pass FALSE.


         Listed below is the demo program DEMLK2.PAS which illustrates many of
         the methods discussed thus far. Figure 9.1 shows the generated output.

         Program DemoLinkTwo;
         {DEMLK2 - creating a linked list}

         Uses CRT,
              totLink;

         var
            GirlFriends: StrDLLOBJ;
            Retcode: integer;
            I : integer;

         begin
            ClrScr;
            with GirlFriends do
            begin
               Init;
               if maxavail > 200 then
               begin
                  Retcode := Add('Erica');
                  Retcode := Add('Theresa');
                  Retcode := Add('Lynn');
                  Retcode := Add('Donna');
                  Retcode := Add('Godzilla');


9-8                                                                 User's Guide

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

                  writeln('Total nodes: ',TotalNodes);
                  writeln('Active node number: ',ActiveNodeNumber);
                  for I := 1 to TotalNodes do
                      writeln(GetStr(NodePtr(I),0,0));
                  writeln;
                  RetCode := InsertBefore(NodePtr(4),'Joyce');
                  writeln('Total nodes: ',TotalNodes);
                  writeln('Active node number: ',ActiveNodeNumber);
                  SwapNodes(NodePtr(5),NodePtr(2));
                  Retcode := Change(StartNodePtr,'Ekka');
                  for I := 1 to TotalNodes do
                      writeln(GetStr(NodePtr(I),0,0));
                  writeln;
                  writeln('Sorted:');
                  Sort(1,true);
                  for I := 1 to TotalNodes do
                      writeln(GetStr(NodePtr(I),0,0));
               end;
            end;
         end.



Figure 9.1                                                              [SCREEN]
Using
StrDLLOBJ



Removing a List

         Calling the method Done will always dispose of all the memory consumed
         by a list. A list can also be emptied with the following method:


         EmptyList;

         Removes all the memory allocated on the heap by chaining back through
         the list and disposing of each node. The ActiveNodePtr, StartNodePtr
         and EndNodePtr are set to nil, and TotalNodes is set to nil.



Comparing StrDLLOBJ to Arrays

         If you have never used linked lists before, but you have used arrays,
         you might be interested in comparing array commands with StrDLLOBJ com-
         mands. Table 9.1 compares the command syntax by manipulating a list of
         5 names.


                        StrDLLOBJ                        String Array



Managing Lists                                                               9-9

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

           Declaring the Variable or Instance
           var NameList: StrDLLOBJ;            var NameList: array[1..5] of
                                               string;

           Initializing the List
           NameList.Init;                      fillchar(NameList,sizeof(NameLis
                                               t),#0);
           Populating the List
           with NameList do                    NameList[1] := 'Mark';
           begin                               NameList[2] := 'Kirk';
              Retcode := Add('Mark');          NameList[3] := 'Lloyd';
              Retcode := Add('Kirk');          NameList[4] := 'Charles';
              Retcode := Add('LLoyd');         NameList[5] := 'Keith';
              Retcode := Add('Charles');
              Retcode := Add('Keith');
           end;

           Writing an item to the display
           with NameList do                    writeln(NameList[3]);
            writeln(GetStr(NodePtr(3),0,0));

           Swapping two items
           with NameList do                    Temp := NameList[4];
            SwapNodes(NodePtr(4),NodePtr(5));  NameList[4] := NameList[5];
                                               NameList[5] := Temp;

           Sorting the List
           NameList.Sort(1,true);              Ugh!

           Erasing the Entries
           NameList.EmptyList;                 fillchar(NameList,sizeof(NameLis
                                               t),#0);




         Don't forget that the StrDLLOBJ is much more memory efficient, and is
         not limited to a set number of elements.

         Much of the power of DLLOBJ is not realized until you create descendant
         objects, customized for your specific needs. Refer to Part 2: Extending
         The Toolkit for further details.



Browse Lists

         Whether your data is in a DLLOBJ (or descendant) object, or in a string
         array, you will want to take advantage of the Toolkit's objects for



9-10                                                                User's Guide

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

         automatically displaying lists in windows. In this section, the Brow-
         seOBJ family of objects will be discussed. These objects display lists
         in a scrollable window.


           Note that a browse window does not include a highlight bar. The
           user browses the contents of the list, but does not select an
           individual element. The ListOBJ family (discussed in the next sec-
           tion) provides objects for highlighting individual elements in a
           list.




         Figure 9.2 shows the object hierarchy for the BrowseOBJ family. The
         base object BrowseOBJ is abstract, and you should not create any
         instances of this type. The BrowseArrayOBJ object displays the contents
         of a string array, and the BrowseLinkOBJ displays the contents of an
         object in the DLLOBJ family. Many programs need to display the contents
         of ASCII files, and the object BrowseFileOBJ makes this a snap.


Figure 9.2                                                             [PICTURE]
BrowseOBJ
Object Hierarchy





Common Methods

         The totLOOK unit includes an object LookTOT which is used to control
         the overall look and feel of applications developed with the Toolkit.
         Call the method LookTOT^.SetListKeys to change the keys which the user
         can press to close the browse window. The defaults are [KEYCAP] and
         [KEYCAP]. Refer to page 3-12 for further details.

         Because all the objects are derived from the BrowseOBJ, they share a
         set of common methods. The following methods can be used with any of
         the browse objects:


         SetTopPick(TopPick: longint);

         This method identifies the node number of the first node that will be
         displayed in the list window.


         SetStartCol(Column: longint);

         This method identifies the character position of the first character of
         the string which is visible in the list window.




Managing Lists                                                              9-11

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

         SetEndCol(Column: longint);

         This method sets the maximum lateral scrolling by identifying the last
         column which will be displayed when the window is scrolled to the right
         maximum. All lateral scrolling can be disabled by calling the Stretch-
         WinOBJ method SetScrollable (discussed later).


         Show;

         This method displays the list and immediately returns control back to
         the calling procedure, i.e. the Toolkit does not wait for user input.
         This option is useful if you want to display the list on the screen,
         but not activate it.


         Go;

         This method is the main "do it" method. If the list is not on display,
         it is drawn. The Toolkit then processes user input until an end key is
         pressed.


         LastKey:word;

         LastKey returns the key code of the key the user pressed to end the
         browse session. This is used if you need to know whether the user
         escaped or pressed the finish key.


         Done;

         As always, Done disposes of the Browse window object, and should always
         be called when the object is no longer required.



Modifying the Window Characteristics

         The list data is displayed in a window of type StretchWinOBJ. You can
         modify any window attribute by directly calling the StretchWinOBJ meth-
         ods. BrowseOBJ includes a function method, Win, which returns a pointer
         to the StretchWinOBJ instance. To modify the window, call the
         appropriate StretchWinOBJ method using the following syntax:

                  [BrowseOBJInstance].Win^.method

         For example, the following statements would change the default window
         size, style and title:

                  var MyBrowser: BrowseLinkOBJ;

                  begin
                     with MyBrowser do
                     begin


9-12                                                                User's Guide

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

                        Init;
                        Win^.SetSize(1,1,50,20,3);
                        Win^.SetTitle('My Browser');
                     end;
                  end.


         All the display colors used by the browse objects are controlled by the
         underlying window attributes. To change the browse display colors call
         the window method SetColors, e.g.

                  Win^.SetColors(23,23,31,30);


         Call Win^ methods to set all the browse window characteristics, e.g.
         colors, title, remove status, boundaries, etc. Refer to chapter 7:
         Using Windows for a thorough discussion of the StretchWinOBJ methods.



Browsing Arrays

         The BrowseArrayOBJ object provides a very simple way to browse the
         contents of a string array. In addition to the common methods just
         described, there is another method as follows:


         AssignList(var StrArray; Total:longint; StrLength:byte);

         AssignList identifies the string array that will be displayed. The
         three parameters are the string array, the total number of elements in
         the array, and the length of each string in the array. The string
         length parameter must reflect the string length of the array when it
         was declared, not the maximum length of any string assigned to the
         array.


         For example, if a string array was declared as follows:

                  MyGirls: array[1..54] of string;

         then the list is assigned with the following calls:

                  with MyBrowser do
                  begin
                     Init;
                     AssignList(MyGirls,54,255);
                     {...}
                  end;



Managing Lists                                                              9-13

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

         Having assigned the list, all you need to do is call Show and the
         browse window will pop onto the display. Listed below is a the demo
         program DEMBR1, followed by figure 9.3 illustrating the resultant dis-
         play.

         Program DemoBrowseArrayOne;
         {SEMBR1}

         Uses DOS,CRT,
              totINPUT, totFAST,totLIST, totSTR;

         var
            BWin: BrowseArrayOBJ;
            StringList: array[1..26] of string[100];
            I : integer;

         begin
            for I := 1 to 26 do  {first assign something to the string array}
               StringList[I] := 'Line '+
                                IntToStr(I)+': '+
                                replicate(80,char(I+64));
            Screen.Clear(white,''); {paint the screen}
            Key.SetFast;
            Key.SetClick(true);
            with BWin do
            begin
               Init;
               AssignList(StringList,26,100);
               Go;
               Done;
            end;
         end.


Figure 9.3                                                              [SCREEN]
Browsing an
Array



         The demo program DEMBR2.PAS, listed below, is very similar to
         DEMBR1.PAS. The only difference is that two Win^ methods are called to
         modify the default window settings. Figure 9.4 shows the impact on the
         display.

         Program DemoBrowseArray;

         Uses DOS,CRT,
              totFAST,totLIST, totSTR;



9-14                                                                User's Guide

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

         var
            BWin: BrowseArrayOBJ;
            StringList: array[1..26] of string[100];
            I : integer;
         begin
            for I := 1 to 26 do  {first assign something to the string array}
               StringList[I] := 'Line '+
                                IntToStr(I)+': '
                                +replicate(80,char(I+64));
            Screen.Clear(white,''); {paint the screen}
            with BWin do
            begin
               Init;
               AssignList(StringList,26,100);
               Win^.SetSize(30,5,50,15,2);
               Win^.SetTitle('Array Browse Demo');
               Go;
               Done;
            end;
         end.


Figure 9.4                                                              [SCREEN]
Modifying the
Browse Window

Browsing Linked Lists

         The BrowseLinkOBJ is very similar to the BrowseArrayOBJ, the primary
         difference being that BrowseLinkOBJ displays the data stored in a
         DLLOBJ object rather than in an array.

         In addition to the common methods described, there are two additional
         methods as follows:


         AssignList(Var LinkList: DLLOBJ);

         This method is passed a DLLOBJ instance, or any instance of an object
         derived from DLLOBJ, e.g. StrDLLOBJ.


         ListPtr: DLLPtr;

         This method returns a pointer to the DLLOBJ used to create the list.
         You can access the linked list by using the syntax MyBrow-
         ser.ListPtr^.method.


         Listed below is the program DEMBR3.PAS, which illustrates how to build
         a large list of 500 items and display it in a browse window. Figure 9.5
         shows the browse display.



Managing Lists                                                              9-15

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

         Program DemoBrowseList;

         Uses DOS,CRT,
              totFAST, totLINK, totLIST, totSTR;

         var
            BWin: BrowseLinkOBJ;
            LL : StrDLLOBJ;

         procedure CreateLinkedList;
         {}
         var
           I, Retcode : integer;
         begin
            with LL do
            begin
               Init;
               for I := 1 to 500 do
                  Retcode := Add('This is line '+IntToStr(I)+
                             ': '+replicate(200,char(random(255))));
            end; {with}
         end; {CreateLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            CreateLinkedList;
            with BWin do
            begin
               Init;
               AssignList(LL);
               Win^.SetTitle('List Browse Demo');
               Go;
               Done;
            end;
            LL.Done
         end.


Figure 9.5                                                              [SCREEN]
Browsing a
StrDLLOBJ



Browsing Files

         The BrowseFileOBJ object makes browsing ASCII text files easy. Browse-
         FileOBJ is descended from BrowseOBJ and inherits all the associated
         methods. In addition, the following method AssignFile is used to
         instruct the object on which file to display:



9-16                                                                User's Guide

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

         AssignFile(Filename:string): integer;

         This function method is passed a string indicating the name of the file
         to be browsed. Behind the scenes, this function loads the file from
         disk into a temporary StrDLLOBJ object. The function will return a 0 if
         the file was successfully loaded. A return value of 1 indicates that
         the file was not found, and so cannot be displayed. A return value of 2
         indicates that there wasn't enough memory to load the entire file. In
         such an instance you may want to use a MessageOBJ to advise the user
         that only part of the file can be browsed.


         That's all there is to it! Just declare a BrowseFileOBJ instance and
         call the methods Init, AssignFile and Go:

                  var FileWin: BrowseFileOBJ;
                  begin
                     with FileWin do
                     begin
                        Init;
                        if AssignFile('Help.TXT') = 0 then
                           Go;
                        Done;
                     end;
                  end.


         The following method can be used to directly access the linked list
         created by the BrowseFileOBJ instance:


         ListPtr: StrDLLPtr;

         This method returns a pointer to the StrDLLOBJ used to create the list.
         You can access the linked list by using the syntax MyFile.ListPtr^.me-
         thod.


         Listed below is the example program, DEMBR4.PAS. This small program
         will display the contents of either a file specified on the command
         line when the program was executed, or (if no parameters were speci-
         fied) the contents of c:\autoexec.bat.



         Program DemoBrowseFile;

         Uses DOS,CRT,
              totFAST, totINPUT, totLINK, totLIST, totSTR;
         const
            DefaultFile = '\autoexec.bat';
         var



Managing Lists                                                              9-17

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

            BWin: BrowseFileOBJ;
            RetCode: integer;
            Filename: string;

         begin
            Screen.Clear(white,''); {paint the screen}
            if ParamCount = 0 then
               FileName := DefaultFile
            else
               FileName := ParamStr(1);
            with BWin do
            begin
               Init;
               Retcode := AssignFile(Filename);
               if Retcode in [0,2] then
                  Go
               else
                  Writeln('Unable to load file: ',Filename,'.');
                  Key.DelayKey(2000);
               Done;
            end;
         end.



9-18                                                                User's Guide

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

Selection Lists

         So far, this chapter has focused on browse windows, where the user can
         simply scroll data vertically and horizontally. By contrast, a list
         window is designed to display selectable items, much like a menu. One
         of the items within the list window is always highlighted, and the list
         may be organized into single or multiple columns. The scroll bars are
         used to move the item highlight bar. The list is removed when the user
         presses an end key such as [KEYCAP] or [KEYCAP], when the user presses
         [KEYCAP], or when the user double-clicks the mouse on an item.

         When appropriate, users may even select, or tag, multiple items from
         the list, and each selected item is marked with a special character.
         The tag status of the highlighted option can be toggled by hitting the
         [KEYCAP]. All items can be tagged or untagged at once by pressing [KEY-
         CAP] and [KEYCAP], respectively.

         Figure 9.6 shows a typical list window.


Figure 9.6                                                              [SCREEN]
A Typical List
Window


         By default, a list window is stretchable, and if the user changes the
         dimensions, the Toolkit adjusts the displayed columns and rows. Figure
         9.7 is the same List window as shown in figure 9.6, after the user has
         stretched the window.


Figure 9.7                                                              [SCREEN]
A Stretched List
Window



         The primary Toolkit object for displaying lists in a window is ListOBJ,
         but this is an abstract object which should not be instanciated. Lis-
         tOBJ has a number of descendants designed to display different data
         types in lists. Figure 9.8 illustrates the ListOBJ object hierarchy.

         The object hierarchy is similar in principle to the BrowseOBJ hierar-
         chy, with the following main objects:

         ListArrayOBJ    Displays a list derived from a string array.

         ListLinkOBJ     Displays a list derived from any DLLOBJ object or
                         descendant.

         ListDirOBJ &    These two objects are used for displaying directories
         ListDirSortOBJ  in a list window and are discussed in the next chapter.



Managing Lists                                                              9-19

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

Figure 9.8                                                             [PICTURE]
ListOBJ
Object Hierarchy



Common Methods

         LookTOT (in the unit totLook) is used to control the overall look and
         feel of applications developed with the Toolkit. As well as the window-
         related methods, the following two LookTOT methods impact all list
         objects:

         SetListKeys     Identifies the keys to globally tag or untag windows,
                         and close the list display.

         SetListChars    Identifies the characters which will highlight the
                         active item, as well as the characters which identify
                         the tagged and non-tagged items.

         Refer to page 3-12 for further details.


         Because all the list objects are derived from the ListOBJ, they share a
         set of common methods. The following methods can be used with any of
         the list objects:


         SetTopPick(TopPick: longint);

         This method identifies the node number of the first node that will be
         displayed in the list window, i.e. the first visible item in the list.
         By default, this is the first item in the list.


         SetActivePick(ThePick:longint);

         The highlighted item is referred to as the active pick. This procedure
         is used to set which item (or pick) will be highlighted when the list
         is first displayed. The passed parameter represents the number of the
         item in the visible window, with the top left item having a value of
         one. For example, if the top pick (the first visible pick) was item
         number 12 and you wanted to highlight the 15th item, pass a value of
         four. This will instruct the Toolkit to highlight the fourth visible
         pick. The default is the top pick. If an invalid value is used, e.g.
         the value is greater than the number of visible picks, the value is set
         to the top pick.


         SetTagging(On:boolean);

         This method is used to identify whether the user will be allowed to tag
         and untag items. Pass true to enable item tagging.




9-20                                                                User's Guide

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

         SetColWidth(Wid:byte);

         This sets the width of each column in characters. If the column width
         is set to zero, the list will be displayed in a single column only, and
         the column width will be as wide as the window. When setting the column
         width, remember to allow for the additional characters used to high-
         light the active pick, and to signify tagged and non-tagged items.


         Show;

         This method displays the list and immediately returns control to the
         calling procedure, i.e. the Toolkit does not wait for user input. This
         option is useful if you want to display the list on the screen, but not
         activate it.


         Go;

         This method is the main "do it" method. If the list is not on display,
         it is drawn. The Toolkit then processes user input until an end key is
         pressed.


         GetHiString:string;

         This function returns a string representing the highlighted item's
         data.


         LastKey:word;

         LastKey returns the key code of the key the user pressed to end the
         browse session. This is used if you need to know whether the user
         escaped or pressed the done key.


         Done;

         As always, Done disposes of the Browse window object, and should always
         be called when the object is no longer required.



Modifying Window Characteristics

         Like BrowseOBJ, the list data is displayed in a window of type Stretch-
         WinOBJ. You can modify any window attribute by directly calling the
         StretchWinOBJ methods.  ListOBJ includes a function method, Win, which
         returns a pointer to the StretchWinOBJ instance. To modify the window,
         call the appropriate StretchWinOBJ method using the following syntax:

                  [ListOBJInstance].Win^.method

         For example, the following statements would change the default window
         size, style and title:

Managing Lists                                                              9-21

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

                  var MyLister: ListLinkOBJ;

                  begin
                     with MyList do
                     begin
                        Init;
                        Win^.SetSize(1,1,50,20,3);
                        Win^.SetTitle('My Lister');
                     end;
                  end.


         Call Win^ methods to set the list window characteristics, e.g. colors,
         title, remove status, boundaries, etc. Refer to chapter 7: Using Win-
         dows for a thorough discussion of the StretchWinOBJ methods.




           Note: the display colors used in list objects are controlled by
           two different methods. The attributes of the list window perime-
           ter, i.e. box, title, scroll bars and icons, are set using the
           method:
                            Win^.SetColors.
           The display attributes for the body of the window, i.e. the items
           in the list, are controlled using the ListOBJ method SetColors,
           which is discussed later in this section.




Listing Arrays

         The ListArrayOBJ object provides an easy way to display a list based on
         the contents of a string array. In addition to the common methods just
         described, there is the following method:


         AssignList(var StrArray;Total:longint;
                    StrLength:byte;Taggable:boolean);

         AssignList identifies the string array that will be displayed. The four
         parameters are the string array, the total number of elements in the
         array, the length of each string in the array, and a boolean to indi-
         cate whether individual items can be tagged. The string length parame-
         ter must reflect the string length of the array when it was declared,
         not the maximum length of any string assigned to the array.


         Having assigned the list, all you need to do is call Show, and the
         browse window will pop onto the display. Listed below is the demo pro-
         gram DEMLS1.PAS.



9-22                                                                User's Guide

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

         program DemoList1;
         {demls1}

         Uses DOS, CRT,
              totFAST, totLIST;

         Var
            Items : array [1..20] of string[30];
            ListWin:  ListArrayObj;

         procedure FillArray;
         {}
         begin
            Items[1] := 'One';
            Items[2] := 'Two';
            Items[3] := 'Three';
            Items[4] := 'Four';
            Items[5] := 'Five';
            Items[6] := 'Six';
            Items[7] := 'Seven';
            Items[8] := 'Eight';
            Items[9] := 'Nine';
            Items[10] := 'Ten';
            Items[11] := 'Eleven';
            Items[12] := 'Twelve';
            Items[13] := 'Thirteen';
            Items[14] := 'Fourteen';
            Items[15] := 'Fifteen';
            Items[16] := 'Sixteen';
            Items[17] := 'Seventeen';
            Items[18] := 'Eighteen';
            Items[19] := 'Nineteen';
            Items[20] := 'Twenty';
         end; {FillArray}

         begin
            Screen.Clear(white,''); {paint the screen}
            FillArray;
            with ListWin do
            begin
               Init;
               AssignList(Items,20,30,true);
               Go;
               Done;
            end;
         end.


         This is a very basic list which uses all the default settings. Figure
         9.9 illustrates the resultant display.



Managing Lists                                                              9-23

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

Figure 9.9                                                              [SCREEN]
A Basic List
Window

         The following few statements are an extract from the demo file
         DEMLS2.PAS which is very similar to the previous demo, except that the
         list window has been customized somewhat:

            with ListWin do
            begin
               Init;
               AssignList(Items,20,30,true);
               SetColWidth(15);
               Win^.SetTitle(' Pick a number! ');
               Win^.SetSize(24,7,55,18,2);
               Go;
               Done;
            end;


         By calling the SetColWidth method, the list has been transformed from a
         single list to a multi-column list. Refer back to figure 9.7 to see the
         output generated from DEMLS2.PAS.

         The program DEMLS3.PAS is yet another refinement of the array list
         demo. In this on-disk file, the following statement is added to change
         the list tagging and highlighting characters:

               LookTOT^.SetListChars(chr(16),chr(17),chr(14),chr(32));



Listing Linked Lists

         The ListLinkOBJ is very similar to the ListArrayOBJ. The primary dif-
         ference is that ListLinkOBJ displays the data stored in a DLLOBJ object
         rather than in an array.

         In addition to the common methods described earlier, there are the
         following two methods:


         AssignList(var LinkList: DLLOBJ);

         This method is passed a DLLOBJ instance, or any instance of an object
         derived from DLLOBJ, e.g. StrDLLOBJ.


         ListPtr: DLLPtr;

         This method returns a pointer to the DLLOBJ used to create the list.
         You can access the linked list by using the syntax MyList.ListPtr^.me-
         thod.




9-24                                                                User's Guide

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

         The demo program DEMLS4.PAS (listed below) shows how well StrDLLOBJ and
         ListLinkOBJ work together. This small demo program creates an StrDLLOBJ
         instance called ItemList. The list is populated by reading the contents
         of an ASCII file. The ListLinkOBJ instance ListWin is then used to
         display the file contents in a window. Figure 9.10 shows the generated
         screen display.

         program DemoList4;
         {demls4 - reading a list from a text file}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST;

         Var
            ListWin:  ListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do
            begin
               Init;
               {$I-}
               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);
               end;
            end;
         end; {LoadLinkedList}



Managing Lists                                                              9-25

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

         begin
            Screen.Clear(white,''); {paint the screen}
            LoadLinkedList;
            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               Win^.SetTitle(' Items from file DEMLS4.TXT ');
               Win^.SetSize(20,5,60,20,1);
               if not FileOk then
                  SetTagging(false);
               Go;
               Done;
            end;
            ItemList.Done;
         end.


Figure 9.10                                                             [SCREEN]
Display a List
From a File



Determining Which Items are Tagged

         Earlier you learned that by calling the method SetTagging(true), you
         could allow the user to tag and untag items. The Toolkit provides the
         method SetStatus to allow you to pre-tag (or, indeed, pre-untag!) items
         before the list is displayed. Similarly, the method GetStatus is used
         to determine the status of any item in the list.

         Before explaining the syntax of these methods, we need to take a little
         peek behind the scenes. The Toolkit stores eight different flags for
         each item in the list. These flags are numbered 0 through 7.  Flag 0 is
         used to determine whether the user has tagged the item. Flag 1 indi-
         cates which color to use in dual color mode (discussed later). The
         remaining flags are not used and can be customized for descendant
         objects. Refer to Part 2: Extending the Toolkit for more information.

         The syntax of the SetStatus method is as follows:


         SetStatus(Pick:longint; BitPos: byte; On:boolean);

         To pre-tag an item, call the SetStatus method and pass three parame-
         ters. The first parameter specifies the item to be modified, the second
         parameter is the flag number (and this should be set to 0 (zero) to
         modify the tag flag), and the third boolean parameter should be set to
         True to tag the item, or False to un-tag it.



9-26                                                                User's Guide

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

         After the user has removed the list window, and before the Done method
         is called, the GetStatus method can be called to see which selections
         the user made. The syntax of GetStatus is as follows:


         GetStatus(Pick:longint; BitPos: byte): boolean;

         This function method returns true if the identified item flag is set
         on. The first parameter identifies the item number, and the second
         parameter should be set to 0 (zero) to get the status of the tag flag.


         The following demo program, DEMLS5.PAS, shows how to check for all the
         tagged items. The program writes a list of all the tagged items.

         program DemoList5;
         {demls5 - selecting all tagged items}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST;

         Var
            ListWin:  ListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;
            L,Total: longint;

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do
            begin
               Init;
               {$I-}
               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);


Managing Lists                                                              9-27

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

               end;
            end;
         end; {LoadLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            LoadLinkedList;
            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               Win^.SetTitle(' Items from file DEMLS4.TXT ');
               Win^.SetSize(20,5,60,20,1);
               if not FileOk then
                  SetTagging(false);
               Go;
               Remove;
               Total := ItemList.TotalNodes;
               clrscr;
               for L := 1 to Total do
                  if GetStatus(L,0) then
                     Writeln('Selected: ',GetString(L,0,0));
               Done;
            end;
            ItemList.Done;
         end.


         In a real application, you might call a procedure to process the tagged
         items, rather than simply write a list of all tagged items.

         If you are displaying a linked list, you can take advantage of the
         DLLOBJ method DelAllStatus. This method is used to automatically remove
         entries from the list which have one of the status flags set in a
         specified state. The syntax of the DLLOBJ method is as follows:


         DelAllStatus(BitPos:byte; On:boolean);

         This method removes all entries from the linked list which have the
         specified flag set in the specified state. The first parameter indi-
         cates the flag number, in the range 0 to 7. The second parameter deter-
         mines whether all the flags set to True or False will be deleted. For
         example, to remove all tagged entries in a linked list you would call
         the method DelAllStatus(0,true);.



9-28                                                                User's Guide

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

Displaying Dual Colored Lists

         All list objects are capable of displaying each item in one of two
         color combinations. This facility is put to good effect in the ListDi-
         rOBJ object (discussed in the next chapter), where the files are dis-
         played in one color, and the subdirectories in another. The following
         two methods impact the list display colors:


         SetColors(HAttr,NAttr,SAttr: byte);

         This method sets the display attributes for the items in the list. The
         first parameter specifies the attribute of the active pick. The other
         two attributes specify the attributes for the normal items. If the
         status (discussed below) of the normal item is set to high, the SAttr
         attribute will be used, otherwise the NAttr will be used.


         SetDualColors(On:boolean);

         If you want to exploit the dual color capabilities of the list objects,
         pass True. Passing a False will force all the items to display in the
         same color.


         The Toolkit has to have some way of identifying the status of each
         item, i.e. in which attribute the color should be displayed. The second
         of the status flags, i.e. flag 1, is designated as the color flag. The
         SetStatus and GetStatus flags are used to control the color feature, as
         follows:


         SetStatus(Pick:longint; BitPos: byte; On:boolean);

         This method is used to set the status of any item in the list. The
         first parameter identifies the number of the item to be set. The second
         parameter should be set to 1 (one) to indicate that the color status
         flag is being set. The final parameter is a boolean to indicate whether
         status is on (True) or off (False).



         GetStatus(Pick:longint; BitPos: byte): boolean;

         This function method returns true if the identified item flag is set
         on. The first parameter identifies the item number, and the second
         parameter should be set to 1 (one) to determine the status of the dual
         color flag.

         An example of the dual color facility is included in the DEMLS7.PAS,
         discussed in the Character Hook section at the end of the chapter.



Managing Lists                                                              9-29

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

Displaying Messages about the Highlighted Pick

         The ListOBJ object family supports the optional display of a message at
         the bottom of the list window. This message is normally related to the
         currently highlighted pick. The message might be a long description of
         a terse pick, or some other information about the highlighted pick.

         There are two different ways to instruct the Toolkit to display a mes-
         sage. The no-fuss way is to write a special procedure (called a message
         hook) and instruct the Toolkit to call this procedure every time a pick
         is highlighted. The alternative is to take advantage of an OOP feature
         known as Polymorphism.

         Once a message displaying routine has been implemented, it can be dis-
         abled and enabled using the following method:


         SetMsgState(On:boolean);

         Pass True to activate the message display, or False to deactivate it.



Using a Message Hook

         A message hook is an external function which is called every time a new
         pick is highlighted. To utilize the message hook facility, all you have
         to do is create a function following some specific rules, and then call
         the method SetMsgHook to instruct the Toolkit to use your function.

         For a function to be eligible as a message hook it must adhere to the
         following rules:

         Rule 1     The function must be declared as a FAR function. This can be
                    achieved by preceding the function with a {$F+} compiler
                    directive, and following the function with a {$F-} direc-
                    tive. Alternatively, Turbo 6 users can use the new keyword
                    FAR following the function statement.

         Rule 2     The function must be declared with one passed parameter of
                    type longint. This parameter indicates the highlighted item
                    number.

         Rule 3     The function must return a value of type string. This return
                    value is the actual text to be displayed in the message area
                    of the window display.

         Rule 4     The function must be at the root level, i.e. the function
                    cannot be nested within another procedure or function.

         The following function declaration follows these rules:



9-30                                                                User's Guide

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

                  {$F+}
                  function MyMessageHook(HiPick:longint): string;
                  begin
                  ...{function statements}
                     MyMessageHook := 'something!';
                  end;
                  {$F-}


         The following method SetMsgHook is then called to instruct the Toolkit
         to call your function every time a new item is highlighted:


         SetMsgHook(Func:ListMsgFunc);

         This method is passed the function name of a function declared using
         the rules outlined above, e.g. SetMsgHook(MyMessageHook);



         The demo program DEMLS6 (listed below) implements a message hook. In
         this case the message is simply a phrase stating which topic is high-
         lighted. I hope your applications are a little more useful! Figure 9.11
         illustrates the resultant output.



         program DemoList6;
         {demls6 - displaying a list message}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST, totSTR, totMSG;

         Var
            ListWin:  ListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;

         {$F+}
         function MsgHook(HiPick:longint):string;
         {}
         begin
            MsgHook := 'The Hi Pick is '+IntToStr(HiPick);
         end; {MsgHook}
         {$F-}

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do



Managing Lists                                                              9-31

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

            begin
               Init;
               {$I-}
               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);
               end;
            end;
         end; {LoadLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            LoadLinkedList;
            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               SetMsgHook(MsgHook);
               Win^.SetTitle(' A List With Messages ');
               Win^.SetSize(20,5,60,20,2);
               Win^.SetMinSize(20,7);
               if not FileOk then
                  SetTagging(false);
               Go;
               Done;
            end;
            ItemList.Done;
         end.


Figure 9.11                                                             [SCREEN]
A List with a Mes-
sage



9-32                                                                User's Guide

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

Creating a Descendant Object

         By design, most of the documentation on customizing the Toolkit with
         object oriented techniques is to be found in Part 2: Extending the
         Toolkit. However, as an appetizer, this section explains how to take
         advantage of OOP to implement your own message method, without needing
         to pass a procedure as a parameter. The message hook procedure
         described in the previous section is perfectly adequate and acceptable,
         but it isn't OOP! If you want to learn a few OOP tidbits, read on,
         otherwise be content with the message hook and skip to the next sec-
         tion.

         Usually, to customize a Toolkit object to better meet your needs, you
         create a descendant object. For illustration, we will modify the List-
         LinkOBJ object. Whenever you create a descendant object, you should
         study the parent object's methods and decide which ones to replace or
         modify.

         A descendant object contains all the data and methods of its parent. As
         a bare minimum, you should always define new Init and Done methods, and
         then replace any other methods that you wish to improve or modify. (You
         can also add new methods, but we'll leave that to Part 2!).

         The ListLinkOBJ method includes the following virtual method:

         function MessageTask(HiPick:longint):string;   VIRTUAL;

         This method is called every time a new topic is highlighted, and it
         returns the text to be displayed. Since this method is declared VIR-
         TUAL, we can replace the method in a descendant object, and other
         methods will use the newly defined version of MessageTask.

         The objective is to create a new object descendant from ListLinkOBJ,
         and replace the MessageTask method with a new routine. The syntax for
         declaring the new object type is as follows:

         Type
         NewListLinkOBJ = object (ListLinkOBJ)
           constructor Init;
           function    MessageTask(HiPick:longint):string;   VIRTUAL;
           destructor  Done;                                 VIRTUAL;
         end; {NewListLinkOBJ}


         The descendant object will be called NewListLinkOBJ. Notice that the
         special constructor and destructor procedures Init and Done are
         declared, as well as the new MessageTask function. The constructor and
         destructor methods are required when an object includes virtual meth-
         ods, as it instructs Turbo Pascal to manage procedure calls differ-
         ently. The keywords constructor and destructor are simply used as
         replacements for procedure. The de facto OOP standard is to name the
         constructor INIT and the destructor DONE.



Managing Lists                                                              9-33

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

         If the ancestor method is declared VIRTUAL, the descendant method must
         also be declared virtual. Furthermore, when you substitute virtual
         methods in a descendant object, the declaration of the method must be
         exactly the same as the ancestor. For this reason, MessageTask and Done
         are both declared virtual, with the same parameters as ListLinkOBJ.

         Having declared a new type NewListLinkOBJ, you must write the actual
         object methods in the body of your program or unit. In this case, Turbo
         Pascal expects the three object methods Init, MessageTask and Done.
         Listed below is an example of how these methods might be written.

         constructor NewListLinkOBJ.Init;
         {}
         begin
            ListLinkOBJ.Init;
            vMsgActive := true;
         end; {NewListLinkOBJ.Init}

         function NewListLinkOBJ.MessageTask(HiPick:longint):string;
         {}
         begin
            MessageTask := 'The Hi Pick is '+IntToStr(HiPick);
         end; {NewListLinkOBJ.MessageTask}

         destructor NewListLinkOBJ.Done;
         {}
         begin
            ListLinkOBJ.Done;
         end; {NewListLinkOBJ.Done}

         Notice that each method starts with the object name followed by a
         period followed by the method identifier. Whenever you declare an Init
         or Done method, you should always call the ancestors Init or Done --
         very often, this ancestor method performs important data initialization
         tasks, and let's not forget that a descendant object assumes all the
         data of its ancestor.

         In the new Init method, the variable vMsgActive is set to true. (All
         object variables in the Toolkit commence with the letter "v".) This
         variable indicates that the object should always call the message dis-
         play method when the active topic is changed. In the LinkListOBJ, this
         variable defaults to false, and is changed to true when the method
         SetMsgHook is called. The main purpose of this descendant object is to
         replace the old MessageTask method with a new one. In the new method,
         the string value returned is just the statement identifying the high-
         lighted topic.

         That's it. Any instance of NewListLinkOBJ will automatically display
         the string returned by the new MessageTask method. Listed below is the
         example DEMLS7.PAS which shows the example in its entirety.



9-34                                                                User's Guide

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

         program DemoList7;
         {demls7 - creating a descendant ListObject}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST, totSTR, totMSG;

         Type
           NewListLinkOBJ = object (ListLinkOBJ)
              {Methods...}
              Constructor Init;
              function    MessageTask(HiPick:longint):string;   VIRTUAL;
              destructor  Done;                                 VIRTUAL;
           end; {NewListLinkOBJ}

         Var
            ListWin:  NewListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;

         {+++++new object methods+++++}
         constructor NewListLinkOBJ.Init;
         {}
         begin
            ListLinkOBJ.Init;
            vMsgActive := true;
         end; {NewListLinkOBJ.Init}

         function  NewListLinkOBJ.MessageTask(HiPick:longint):string;
         {}
         begin
            MessageTask := 'The Hi Pick is '+IntToStr(HiPick);
         end; {NewListLinkOBJ.MessageTask}

         destructor NewListLinkOBJ.Done;
         {}
         begin
            ListLinkOBJ.Done;
         end; {NewListLinkOBJ.Done}
         {+++++end of new object methods+++++}

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do
            begin
               Init;
               {$I-}



Managing Lists                                                              9-35

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

               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);
               end;
            end;
         end; {LoadLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            LoadLinkedList;
            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               Win^.SetTitle(' A List With Messages ');
               Win^.SetSize(20,5,60,20,2);
               Win^.SetMinSize(20,7);
               if not FileOk then
                  SetTagging(false);
               Go;
               Done;
            end;
            ItemList.Done;
         end.


         This example works just like DEMLS6.PAS, but a descendant object was
         created rather than passing a procedure. I know what you are thinking
         "It's a damn sight easier to pass a procedure!". That's true, and that
         is precisely why the Toolkit includes the procedure passing alterna-
         tive. By design, this first taste of OOP was a little simple. If your
         curiosity has been piqued, review the source code in the unit
         TOTLIST.PAS. You will see how the base object ListOBJ does all the
         work, and how the descendant objects ListArrayOBJ and ListLinkOBJ make
         the objects work for string arrays and linked lists. The totDIR unit
         discussed in the next chapter extends the ListLinkOBJ and customizes it
         specifically for displaying files and directories.



9-36                                                                User's Guide

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

Character Hooks

         The ListOBJ object family provides ways to intercept every key pressed
         by the user. This allows you to implement your own special hotkeys.

         Like the Message facility described earlier, there are two different
         ways to instruct the Toolkit to call a procedure every time a key is
         pressed -- by passing a procedure, or by creating a descendant object.



Using a Character Hook

         A character hook is an external procedure which is called every time a
         key or mouse button is pressed. To utilize the character hook facility,
         all you have to do is create a function following some specific rules,
         and then call the method SetCharHook to instruct the Toolkit to use
         your function.

         For a function to be eligible as a character hook it must adhere to the
         following rules:

         Rule 1     The function must be declared as a FAR function. This can be
                    achieved by preceding the function with a {$F+} compiler
                    directive, and following the function with a {$F-} direc-
                    tive. Alternatively, Turbo 6 users can use the new keyword
                    FAR following the function statement.

         Rule 2     The function must be declared with four (count 'em) passed
                    parameters. Parameter one must be a variable parameter of
                    type word. This parameter indicates which key the user just
                    pressed, and you may change the value of this parameter to
                    substitute a different key. The second and third parameters
                    must be variable parameters of type byte, and they represent
                    the X and Y coordinates of the mouse at the time the key was
                    pressed. The fourth parameter is non-variable, and must be
                    of type longint. This parameter indicates the highlighted
                    item number.

         Rule 3     The function must return a value of type tListAction. This
                    is an enumerated type which indicates to the Toolkit how to
                    proceed. The members of the enumerated type are: Finish,
                    Refresh and None. If you want the list window to terminate,
                    return Finish. If you have changed, inserted, or deleted any
                    items in the visible list, return Refresh. The Toolkit will
                    then re-display the entire window contents. Normally, how-
                    ever, you will just return None.

         Rule 4     The function must be at the root level, i.e. the function
                    cannot be nested within another procedure or function.

         The following function declaration follows these rules:



Managing Lists                                                              9-37

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

                  {$F+}
                  function MyCharHook(var K:word;
                                         var X,Y: byte;
                                         HiPick:longint): tListAction;
                  begin
                  ...{function statements}
                     MyCharHook := None;
                  end;
                  {$F-}


         The following method SetCharHook is then called to instruct the Toolkit
         to call your function every time a key is pressed:


         SetCharHook(Func:ListCharFunc);

         This method is passed the function name of a function declared using
         the rules outlined above, e.g. SetCharHook(MyCharHook);.


         The demo program DEMLS8.PAS, listed below, implements both a message
         hook and a character hook. The character hook is checking for two spe-
         cial keys. If [KEYCAP] is pressed, a simple help screen is displayed,
         and if [KEYCAP] is pressed, the topic is changed to the alternative
         color.

         program DemoList8;
         {demls8 - using Message and Character hooks}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST, totSTR, totMSG;

         Var
            ListWin:  ListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;

         {$F+}
         function HelpHook(var K:word; var X,Y: byte; HiPick:longint): tListAc-
         tion;
         {}
         var MsgWin: MessageOBJ;
         begin
            HelpHook := None;
            if K = 315 then
            begin
               with MsgWin do
               begin
                  Init(6,'Kinda Help');
                  AddLine('');
                  AddLine('In a real application, this would');



9-38                                                                User's Guide

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

                  AddLine('be a help screen, and it would give');
                  AddLine('help related to item '+IntToStr(HiPick)+'!');
                  AddLine('');
                  Show;
                  Done;
               end;
               K := 0;
            end
            else if K = 316 then {F2 so swap colors}
            begin
               ListWin.SetStatus(HiPick,1,not ListWin.GetStatus(HiPick,1));
               K := 336; {emulate down cursor}
            end;
         end; {HelpHook}

         function MsgHook(HiPick:longint):string;
         {}
         begin
            MsgHook := 'The Hi Pick is '+IntToStr(HiPick);
         end; {MsgHook}
         {$F-}

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do
            begin
               Init;
               {$I-}
               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);



Managing Lists                                                              9-39

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

               end;
            end;
         end; {LoadLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            Screen.WriteCenter(25,white,'  F1 Help   F2 Toggle Color!   [Space]
         Toggle Tag  ');
            LoadLinkedList;
            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               SetCharHook(HelpHook);
               SetMsgHook(MsgHook);
               SetDualColors(true);
               Win^.SetTitle(' A Multi-Colored List ');
               Win^.SetSize(20,5,60,20,2);
               Win^.SetMinSize(20,7);
               if not FileOk then
                  SetTagging(false);
               Go;
               Done;
            end;
            ItemList.Done;
         end.


Figure 9.12                                                             [SCREEN]
Using a
Character Hook

Creating a Descendant Object

         I think we've had enough OOP for one chapter. Suffice it to say that
         just like the message hook, the character hook can be implemented by
         creating a descendant object and replacing a ListLinkOBJ method called
         CharTask.

         The demo file DEMLS9.PAS (listed below) shows how to replace both the
         character hook and the message hook by creating a new descendant
         object.

         program DemoListNine;
         {demls9 - extending the LinkListOBJ object}

         Uses DOS, CRT,
              totFAST, totLINK, totLIST, totSTR, totMSG;



9-40                                                                User's Guide

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

         Type
           NewListLinkOBJ = object (ListLinkOBJ)
              {Methods...}
              Constructor Init;
              function    MessageTask(HiPick:longint):string;       VIRTUAL;
              function    CharTask(var K:word; var X,Y: byte;
                                   HiPick:longint): tListAction;    VIRTUAL;
              destructor  Done;                                     VIRTUAL;
           end; {NewListLinkOBJ}

         Var
            ListWin:  NewListLinkObj;
            ItemList: StrDLLOBJ;
            FileOK: boolean;

         {+++++new object methods+++++}
         constructor NewListLinkOBJ.Init;
         {}
         begin
            ListLinkOBJ.Init;
            vMsgActive := true;
         end; {NewListLinkOBJ.Init}

         function  NewListLinkOBJ.MessageTask(HiPick:longint):string;
         {}
         begin
            MessageTask := 'The Hi Pick is '+IntToStr(HiPick);
         end; {NewListLinkOBJ.MessageTask}

         function NewListLinkOBJ.CharTask(var K:word; var X,Y: byte;
                                          HiPick:longint): tListAction;
         {}
         var MsgWin: MessageOBJ;
         begin
            CharTask := none;
            if K = 315 then
            begin
               with MsgWin do
               begin
                  Init(6,'Kinda Help');
                  AddLine('');
                  AddLine('In a real application, this would');
                  AddLine('be a help screen, and it would give');
                  AddLine('help related to item '+IntToStr(HiPick)+'!');
                  AddLine('');
                  Show;
                  Done;
               end;
               K := 0;



Managing Lists                                                              9-41

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

            end
            else if K = 316 then {F2 so swap colors}
            begin
               ListWin.SetStatus(HiPick,1,not ListWin.GetStatus(HiPick,1));
               K := 336; {emulate down cursor}
            end;
         end; {NewListLinkOBJ.CharTask}

         destructor NewListLinkOBJ.Done;
         {}
         begin
            ListLinkOBJ.Done;
         end; {NewListLinkOBJ.Done}
         {+++++end of new object methods+++++}

         procedure LoadLinkedList;
         {}
         var
           F: text;
           Line:string;
           Result: integer;
         begin
            with ItemList do
            begin
               Init;
               {$I-}
               Assign(F,'demls4.txt');
               Reset(F);
               {$I+}
               FileOK := (IOResult = 0);
               if not FileOK then
                  Result := Add('File not found')
               else
               begin
                  while not eof(F) do
                  begin
                     Readln(F,Line);
                     Result := Add(Line);
                  end;
                  close(F);
               end;
            end;
         end; {LoadLinkedList}

         begin
            Screen.Clear(white,''); {paint the screen}
            Screen.WriteCenter(25,white,'  F1 Help   F2 Toggle Color!   [Space]
         Toggle Tag  ');
            LoadLinkedList;



9-42                                                                User's Guide

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

            with ListWin do
            begin
               Init;
               AssignList(ItemList);
               SetColWidth(15);
               SetDualColors(true);
               Win^.SetTitle(' A Multi-Colored List ');
               Win^.SetSize(20,5,60,20,2);
               Win^.SetMinSize(20,7);
               if not FileOk then
                  SetTagging(false);
               Go;
               Done;
            end;
            ItemList.Done;
         end.
