TBINFO.TXT  by Hannes Ziegler CIS 100142,302

*********
Contains:

A description of the internal TBrowse-structure as far as I know it.
(I don't work with CA. I hacked it <g>).

*************
Introduction:
  
TBrowse is the most flexible object in Clipper and is widely used.
However, it's design does not fit into my OOP-understanding: too many
things are unflexible/lacking and too many things are hidden to the
programmer. With Class(y) we can inherit from TBrowse and we can add
missing features, but Class(y) still does not give full access to TBrowse.
I'm not speaking of TBrowse methods, this is code which can be overridden
using Class(y). This file describes instance variables (iVars), that are
not accessible in normal situations.

****************
Basic structure:

A programmer who is using Class(y) knows that iVars are being stored in
an array. The same is true for TBrowse or any Clipper-object. The basic
structure of Tbrowse's iVar array can be examined in CLD using

   array := acopy( oTBrowse , array( len(oTBrowse) ) )

Here len(oTBrowse) is essential because the structure of TBrowse changed
from Clipper 5.01 to 5.2 . Since iVars are being stored in an array it
seems to be logical, that the array-element-operator [] can be applied to
an object. As a matter of fact it is possible and yields following
results if applied to TBrowse:

Element           is iVar in version 5.01
                  
oTBrowse[1] ->    Cargo
oTBrowse[2] ->    { nTop, nLeft, nBottom, nRight }
oTBrowse[3] ->    { TBColumn-objects }
oTBrowse[4] ->    ColorSpec
oTBrowse[5] ->    { HeadSep, ColSep, FootSep }
oTBrowse[6] ->    SkipBlock
oTBrowse[7] ->    GoTopBlock
oTBrowse[8] ->    GoBottomBlock
oTBrowse[9] ->    Internals (virtual screen, RowPos, ColPos aso. )

Element            is iVar in version 5.2
                   
oTBrowse[ 1] ->    Cargo
oTBrowse[ 2] ->    nTop
oTBrowse[ 3] ->    nLeft
oTBrowse[ 4] ->    nBottom
oTBrowse[ 5] ->    nRight
oTBrowse[ 6] ->    { TBColumn-objects }
oTBrowse[ 7] ->    HeadSep
oTBrowse[ 8] ->    ColSep
oTBrowse[ 9] ->    FootSep
oTBrowse[10] ->    ColorSpec
oTBrowse[11] ->    SkipBlock
oTBrowse[12] ->    GoTopBlock
oTBrowse[13] ->    GoBottomBlock
oTBrowse[14] ->    Internals

The changes from 5.01 to 5.2 are the two subarrays for coordinates and
separator-chars: they don't exist anymore, and TBColumn-objects have
moved in the array structure. The real interesting (hidden) part of
TBrowse is stored in the last element of the iVar array, and this can
be accessed using atail(oTBrowse) regardless of the Clipper version a
program has been compiled with. So this is what I am looking into:
the last element of a TBrowse!

****************
Atail(oTBrowse):

The most interesting part of atail(oTbrowse) is: it contains a string
that can be assigned to a LOCAL or STATIC or whatever variable. But you
don't get a string-copy (as usual with strings) you get an assignment by
reference. Whatever TBrowse is changing in atail(oTbrowse) will be
reflected in the variable that holds the reference to atail(oTBrowse).
Keep in mind, if you have a xyz:=atail(oTBrowse), then xyz is of
valtype()=="C" but it is being changed by oTBrowse:stabilize(), which
can occur elsewhere in a program. xyz contains a reference to a string
(or array-element)! Now, what's inside atail(oTbrowse)?
Basically the last element of TBrowse has five major parts:

A. 84 bytes containing exported iVars and internal information
B.  x bytes virtual screen
C.  y bytes one empty data row (ghost record)
D. 12*ColPos Bytes column descriptions
E. ColCount*RowCount*2 bytes color information for ColorRect()

Part A has the real interesting information, it's 42 pairs of bytes
that can be decoded with bin2i(). I don't know them all, and most of
them don't enhance the knowledge about TBrowse. But with some of these
bytes nice things are possible.
I just give the offset for bin2i( substr( atail(oTBrowse), *nOffSet* ) )
because this is required to read the info anyway. What I don't know is
missing, what I'm not sure about has a question mark:

 1 - len( atail( oTBrowse ) )
 5 - first column ?
 7 - last column ?
 9 - first unfrozen column
11 - last column ?
13 - len( virtual screen string )
21 - rightmost screen column for display (oTBrowse:nRight)
23 - len( one line of data from 1 to ColCount ) [referred to as BlockSize]
25 - len( one line of data for frozen columns only )
27 - TB : RowCount
29 - BlockNo for string that is HeadSep
31 - BlockNo for string that is FootSep
33 - BlockNo first data row in virtual screen
35 - BlockNo last  data row in virtual screen
37 - BlockNo last row with data in virtual screen [eg: if RowCount() > lastrec()]
39 - TB : ColPos
41 - TB : RowPos
43 - BlockNo current data row
45 - BlockNo first footing row
47 - pending skip
51 - no of spaces left of first unfrozen column
53 - offset in virtual screen for data row of visible unfrozen columns 
55 - len( data row of visible unfrozen columns )
57 - TB : Freeze
59 - TB : leftVisible
61 - TB : rightVisible
63 - Contains chr(2) if configure() is needed.
     example: TB:DelColumn(n) does not *!* rebuildt the virtual screen
              instead byte 63 is set to chr(2).
65 - TB : HitTop  chr(1) => .T.
67 - TB : HitBottom  chr(1) => .T.
69 - TB : Stable  chr(1) => .T.
71 - TB : AutoLite  chr(1) => .T.
73 - TB : HiLite  chr(1) => .T.
75 - BlockNo to start from for RefreshAll()

85 - from here on you find Part B

You see that many so called 'exported' iVars of TBrowse reside in
atail(oTbrowse) which are being accessed via a method (We get fooled
from the compiler. This is what I don't like in the CA TBrowse-design.
Ok, an object hides it's information, but why the heck is HitBottom
named as an exported iVar in NG files? It's a method! Compare it
with nTop: it has hungarian notation and is stored in the iVar array).

What can be done with the first 84 bytes? Well, I have not yet discovered
it all but for sure there are two real interesting bytes: No 47-48.
TBrowse knows how many skips are pending, how many records have not been
displayed at screen yet. Some other valuable information can be retrieved,
like: How many rows are being used for Heading/Footing? What are the
exact screen coordinates for the data area only? (see TBINTE.ZIP for code)
How many blanks are being padded left to the first unfrozen column? 
Do all columns fit into the browse area? There are lots of clumsy code
examples flying around just to answer such questions. It's easy if 
atail(oTBrowse) is being queried.

Next is part B which does the real Tbrowse-overhead to a program.


Part B == virtual screen

Beginning at byte 85 you find the virtual screen that is being maintained
by TBrowse. This screen is being buildt during stabilize() and it's a
string that contains all data of all columns and rows. The size of the
string depends on ColCount and RowCount. The larger they are, the longer
stabilize() will take.
Right after an AddColumn() the virtual screen contains Heading / Footing /
HeadSep / FootSep of all columns (including the new one), plus one empty
data row at the end. During a stabilize() only the data rows in the
virtual screen are being updated, whereas after configure() or invalidate()
Heading and Footing areas are being refreshed also.
The virtual screen is being buildt row by row and for this, bytes No 23-24
in Part A are essential: they contain the length of one row of data in the
virtual screen (it's one Block of data => BlockSize). All other bytes that
point into the virtual screen must be multiplied with i2bin( byte 23-24 ).
With this, the access to the virtual screen is a snap. (I do it to
draw ColSep in the Heading/Footing area of TBrowse, see TBINTE.ZIP).
The length of the virtual screen is bin2i(substr(atail(oTBrowse),13))).
This contains a ghost record which is Part C of atail(oTBrowse).
It is being used to fill the browse area at screen if eof() has been
encountered. After the virtual screen is one single byte (I don't know
what it's good for) but it is important to know the additional offset
in order to access Part D.

****************************
Part D == Column information
This part is a string after the virtual screen and it contains 12 bytes
for each TBColumn-object in a TBrowse. To my knowledge the meaning is as
follows:
1-2  ColumnWidth
3-4  Offset in virtual screen string
5-6  ??
7-8  len( data to display )
9    DefColor low byte
10   DefColor high byte
11-12 ??

******************************
Part E is used for ColorRect() 

It indicates in binary mode a low/hi color for each single cell in the
whole virtual screen. For each cell 2 bytes are used. So if you have a
TBrowse with 20 columns and 20 rows this part will take 20*20*2=800 bytes
on its own, not to speak from the visrtual screen (as an example:
I once saved a TBrowse to disk that displayed 21 columns of an address
DBF. It was approximately 6500 Bytes for the virtual screen plus
840 bytes for the ColorRect() information. That is TBrowse's overhead)


**********
Conclusion

Well, that's what I know. If you have questions, feel free to ask.
All is for your information only! You can access TBrowse internals
with this description, but if you do it, you know it's a NO-NO in
OOP-world and you do it on your own risk. I do it, because TBrowse
knows more than CA is willing to let me know. I don't give a damn
and I never had a problem so far. If you do it and you have a problem,
don't blame me . atail(oTBrowse) is *not* supposed to be accessed!
(I suppose <g>)

Now I add some lines of code which I use:

Get pending skips (great for mouse control)
		nPending := bin2i(substr(scTBInternal,47))

Determine number of headlines before first data row (includes Headsep)
    nHeadLines := bin2i(substr(atail(oTbrowse),29))

Determine number of footlines after last data row (includes Footsep)
    nStart     :=bin2i(substr(atail(oTbrowse),37))
    nFootLines :=nStart-bin2i(substr(atail(oTbrowse),31))

Many of you know the last two examples already, they are in TBINTE.ZIP :
I draw ColSep in the heading/footing area by accessing the virtual screen
of TBrowse. Looks nice and works fine, unless...

Well, there is a real drawback in the whole stuff (and this I don't like
because it's a flaw in TBrowse design). It's byte No 63 that indicates if
a configure() is needed. The point is: if you issue a TBrowse:DelColumn(1)
and the first column contributes let's say 25 bytes to the virtual screen
I would expect, that the virtual screen would be reduced by 25*RowCount
bytes. This does not happen. A deleted Column still exists in the virtual
screen until you issue a oTbrowse:configure().
So all calculations based on byte 23 may be invalid if byte 63 contains
chr(2). In this case a oTBrowse:configure() is necessary.

That's it. If you can fill in the gaps in the description, I would be
glad if you'd let me know.

Hannes