TBMORE.TXT by Hannes Ziegler CIS 100142,302 Update for TBINFO.ZIP TBINFO.ZIP has errors and contains an incomplete description Date 10/19/93 ******** Content: A description of the internal TBrowse-structure as far as I know it, plus code of 10 funcs I use to access TBrowse-internals ************* 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 eight 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. 2*total rows bytes (flags) for reading and displaying data F. 2*total rows bytes (flags) for displaying data only G. ColCount*RowCount*2 bytes colour information for ColorRect() H. 8 Bytes for each colour pair (foreground/background) in ColorSpec 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'm not sure about has a question mark: 1 - len( atail( oTBrowse ) ) 3 - Total of colour pairs in ColorSpec 5 - first column ? 7 - last column ? 9 - first unfrozen column 11 - last column ? 13 - len( virtual screen string ) 15 - TB : nTop 17 - TB : nLeft 19 - TB : nBottom 21 - TB : 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 (-1 if HeadSep is missing) 31 - BlockNo for string that is FootSep (-1 if ColSep is missing) 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 49 - ? 51 - number 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 - Flag for redisplay of virtual screen (causes automatic RefreshAll()) -1 configure() was received 0 nothing needed 1 colour string was assigned to ColorSpec (configure colours only) 2 ColCount() changed (rebuildt virtual screen) 65 - TB : HitTop 0 = .F. , 1 = .T. 67 - TB : HitBottom 0 = .F. , 1 = .T. 69 - TB : Stable 0 = .F. , 1 = .T. 71 - TB : AutoLite 0 = .F. , 1 = .T. 73 - TB : HiLite 0 = .F. , 1 = .T. 75 - Flag for RefreshAll() 77 - Pointer to part E in atail(oTBrowse) (must add offset of 85) 79 - Pointer to part F in atail(oTBrowse) (must add offset of 85) 81 - Pointer to part G in atail(oTBrowse) (must add offset of 85) 83 - Pointer to part H in atail(oTBrowse) (must add offset of 85) 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. 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? 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() 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 (eg: I do it to draw ColSep in the Heading/Footing area of TBrowse). The length of the virtual screen is bin2i(substr(atail(oTBrowse),13))). This includes a ghost record which is Part C of atail(oTBrowse). The ghost record basically is one row of spaces that contains ColSep. It is being used to fill the browse area at screen if eof() has been encountered. **************************** 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 & F Both parts have identical structure but different meaning for the screen update during a stabilize() loop. They only contain flags (0 or 1) to indicate wether or not a row at screen needs an update from the virtual screen or from TBrowse's data source. The length of each part is 2*height of the browse area = 2*(nRight-nTop+1). I found it interesting to watch both parts in CLD during a stabilize() because it gave a thorough understanding of the internal works. Maybe you like it too. If so, assign atail(oTBrowse) to a public var and set watchpoints in CLD using the following at the command line: ? nLen := 2* (oTBrowse:nBottom - oTBrowse:nTop + 1) ? c := atail(oTbrowse) ? nE := 85 + bin2i(substr( c,77 )) ? nF := 85 + bin2i(substr( c,79 )) wp substr( c ,nE, nLen ) wp substr( c ,nF, nLen ) Then you can experiment with sending messages to Trowse from the command line and step through a stabilization cycle (eg: try invalidate(), RefreshAll(), refreshCurrent(), panright(), panleft() and see what happens. If you send configure(), then you must reassign c := atail(oTbrowse) ). ****** Part G This part is used for ColorRect() only. 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 virtual 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! Part G can be accessed with the result, that colours in the browse region are being toggled. It is possible to toggle single cells that are in the heading/footing region which is not possible using the ColorRect() method. The start of the colour information is given by nStart := 85 + bin2i(substr(atail(oTBrowse),81)) ****** Part H The last part contains ColorSpec in binary format. Each colour in ColorSpec is represented by 4 bytes ( "W/N" is two colours = 8 bytes ). The first three bytes are the base colour and the 4th byte is the enhanced/blink flag. I Clipper code it is like follows: chr( 0)+chr( 0)+chr( 0) => 0 or "N" chr( 0)+chr( 0)+chr(255) => 1 or "B" chr( 0)+chr(255)+chr( 0) => 2 or "G" chr( 0)+chr(255)+chr(255) => 3 or "BG" chr(255)+chr( 0)+chr( 0) => 4 or "R" chr(255)+chr( 0)+chr(255) => 5 or "RB" chr(255)+chr(255)+chr( 0) => 6 or "GR" chr(255)+chr(255)+chr(255) => 7 or "W" The fourth byte can be: chr( 0) => base colour chr( 32) => enhanced colour "+" flag chr(128) => blink colour "*" flag chr(160) => enhanced blink colour "*+" flag ********** Conclusion Well, that's what I know and I stopped hacking now, although few bytes are still missing. If you have questions, feel free to ask via CI$. 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 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! Now I add some lines of code which I use: *********************************************************************** /* TBUTIL.PRG by Hannes Ziegler CS 100142,302 Content: Func TBPending( oTBrowse ) Func TBHeadRows( oTBrowse ) Func TBFootRows( oTBrowse ) Func TBScreenLen( oTBrowse ) Func TBRowLen( oTBrowse ) Func TBToggleColor( oTBrowse, aColor1, aColor2 ) Func TBColorSpec( oTBrowse, cNewColor ) Func TBColorRect( oTBrowse, aTLBR, aColor ) Func TBHeadColSep( oTBrowse ) Func TBFootColSep( oTBrowse ) See descriptions below. Disclaimer: All functions are accessing TBrowse-internals that are not supposed to be accessed. If you use these funcs, you know that you are doing non-OOPy stuff. All funcs have been tested using Clipper 5.01 and 5.2 but if you have a problem using the funcs, it's your's. (it may be 'illegal', but it works ). */ ********************* // Some basics first ********************* ***************************************** // Number of pending skips that must be // performed before TBrowse is stable ***************************************** Func TBPending(oTBrowse) return bin2i(substr(atail(oTbrowse),47)) ***************************************** // Number of rows used for heading region // excluding TBrowse/TBColumn:headSep // ( Count starts at 0 !!) ***************************************** Func TBHeadRows( oTBrowse ) local nHeadSepRow := bin2i(substr(atail(oTbrowse),29)) if nHeadSepRow < 0 nHeadSepRow := bin2i(substr(atail(oTbrowse),33)) end return nHeadSepRow ****************************************** // Number of rows used for footing region // excluding TBrowse/TBColumn:footSep ****************************************** Func TBFootRows( oTBrowse ) local nFirstFootRow := bin2i(substr(atail(oTbrowse),45)) return oTBrowse:nBottom-oTBrowse:nTop+1 - nFirstFootRow **************************************** // Length of virtual screen in Tbrowse // including ghost record **************************************** Func TBScreenLen( oTBrowse ) return bin2i(substr(atail(oTbrowse),13)) *************************************************** // Length of one full data row in Tbrowse // Does not account for restricted TBColumn:Width *************************************************** Func TBRowLen( oTBrowse ) return bin2i(substr(atail(oTbrowse),23)) ************************************************************************* // // Toggle colours using pointers to ColorSpec // oTBrowse:ColorSpec := "W+/BG,W+/R,W+/G,W/B,N/G,B+/BG" // // TBToggleColor( oTBrowse, { {1,2},{3,2} }, { {4,5},{6,5} } ) // // Change colours in browse region without the overhead of refreshAll() // oTBrowse:ColorSpec remains unchanged // *********************************************************************** Func TBToggleColor( oTBrowse, aColor1, aColor2 ) local nStart := 85+bin2i(substr(atail(oTbrowse),81)) local nLength := 85+bin2i(substr(atail(oTbrowse),83)) - nStart local cColorRect:=substr(atail(oTbrowse),nStart, nLength ) local cOriginal, cBytes1, cBytes2, i, nLen nLen := min(len(aColor1),len(aColor2)) for i:=1 to nLen cBytes1 := chr( aColor1[i,1] ) + chr( aColor1[i,2] ) // Match-color cBytes2 := chr( aColor2[i,1] ) + chr( aColor2[i,2] ) // Result-color cColorRect:= strtran( cColorRect, cBytes1, cBytes2 ) next cOriginal := substr(atail(oTBrowse),1) // get a true string copy oTbrowse[len(oTbrowse)]:= stuff( cOriginal, nStart, nLength, cColorRect ) oTbrowse : invalidate() while ! oTbrowse:stabilize() ; end oTbrowse[len(oTbrowse)] := cOriginal return oTBrowse *********************************************************************** // // Change colours in browse region without the overhead of refreshAll() // TBrowse:ColorSpec is changed to new colour. The new colour string // MUST have the same number of color-values as the old one has // // TBColorSpec( oTBrowse, "W/B,N/G,B+/BG" ) // // In the func a colour-string is translated to binary format and // then it is stuffed into TBrowse // ************************************************************************ Func TBColorSpec( oTBrowse, cNewColor ) local aBinary:={; chr( 0)+chr(255)+chr(255),; // BG start with chr(255)+chr( 0)+chr(255),; // RB 2 char colours! chr(255)+chr(255)+chr( 0),; // GR chr( 0)+chr( 0)+chr( 0),; // N chr( 0)+chr( 0)+chr(255),; // B chr( 0)+chr(255)+chr( 0),; // G chr(255)+chr( 0)+chr( 0),; // R chr(255)+chr(255)+chr(255) } // W local aPalette:={"BG","RB","GR","N","B","G","R","W"} local i, cNewBinary:="", nColorCount, cTail nColorCount := 8 * bin2i(substr(atail(oTBrowse),3)) // 8 bytes per colourpair cNewColor := cNewBinary := upper(strtran(cNewColor," ","")) // change base colours to binary for i:=1 to 8 if aPalette[i] $ cNewBinary cNewBinary := strtran( cNewBinary, aPalette[i], aBinary[i]+chr(0) ) end next // get rid of slash and commas cNewBinary := strtran(strtran(cNewBinary,"/",""),",","") // change blink- and enhanced flags to binary if "+*" $ cNewBinary cNewBinary := strtran(cNewBinary,chr(0)+"+*",chr(160)) end if "*+" $ cNewBinary cNewBinary := strtran(cNewBinary,chr(0)+"*+",chr(160)) end if "*" $ cNewBinary cNewBinary := strtran(cNewBinary,chr(0)+"*",chr(128)) end if "+" $ cNewBinary cNewBinary := strtran(cNewBinary,chr(0)+"+",chr(32)) end // Make sure not to change len(atail(oTbrowse)) while len(cNewBinary) < nColorCount cNewBinary+=replicate(chr(0),8) cNewColor +=",N/N" end // Assign ColorSpec oTbrowse:ColorSpec := cNewColor // Reset internal flag that causes RefreshAll() cTail := atail(oTBrowse) cTail := stuff(cTail,63,1,chr(0)) // Change binary colour information oTBrowse[len(oTBrowse)] :=; substr(cTail,1,len(cTail)-nColorCount)+left(cNewBinary,nColorCount) oTBrowse:invalidate() return oTbrowse ********************************************************************* // // Change colours in the entire browse region, not only data rows. // TBColorRect() is the same as TBrowse:ColorRect() but it is extended // to heading/footing as well. // // TBColorRect( oTBrowse, aTLBR, aColor ) // // Here aTLBR is a 4-elem array that points to all rows starting at 1 // and ending at nHeight => oBrowse:nBottom-oTbrowse:nTop+1 // // Assume there are heading and footing rows. // This changes colours only in heading / footing : // // TBColorRect(oTBrowse,{1,1,TBHeadRows(oTBrowse),oTBrowse:ColCount},{2,1}) // // nRows:= TBFootRows( oTBrowse ) // nB := oTBrowse : nBottom // TBColorRect( oTBrowse, {nB-nRows+1,1,nB,oTBrowse:ColCount} , {2,1} ) // ********************************************************************* Func TBColorRect( oTBrowse, aTLBR, aColor ) local nStart := 85+bin2i(substr(atail(oTbrowse),81)) local nLength := 85+bin2i(substr(atail(oTbrowse),83)) - nStart local cColorRect:= substr(atail(oTbrowse),nStart, nLength ) local cOriginal, cBytes, i, nWidth, nTotal nTotal := 2 * oTBrowse:ColCount nWidth := (aTLBR[4]-aTLBR[2]+1) cBytes := replicate( chr(aColor[1]) + chr(aColor[2]), nWidth ) nWidth *= 2 for i:=aTLBR[1] to aTLBR[3] cColorRect:= stuff( cColorRect, 1+(nTotal*(i-1)), nWidth, cBytes ) next cOriginal := substr(atail(oTBrowse),1) // get a true string copy oTbrowse[len(oTbrowse)]:= stuff( cOriginal, nStart, nLength, cColorRect ) oTbrowse : invalidate() return oTBrowse //*********************************************************************** // Func TBHeadColSep(oTBrowse) // Func TBFootColSep(oTBrowse) // // These funcs draw Colsep in the Heading/Footing region of a TBrowse. // Try: // // oTBrowse : HeadSep := "อุอ" // oTBrowse : FootSep := "อุอ" // oTBrowse : ColSep := " ณ " // // After oTBrowse:AddColumn() and prior to oTBrowse:stabilize() // pass oTbrowse to one or both funcs // // After stabilize() the TBrowse may look like // // Heading ณ Heading ณ Heading ณ Heading // -SubHead ณ -SubHead ณ -SubHead ณ -SubHead // ออออออออออุออออออออออุออออออออออุออออออออออ // Data12 ณ Data12 ณ Data12 ณ Data12 // Data13 ณ Data13 ณ Data13 ณ Data13 // Data14 ณ Data14 ณ Data14 ณ Data14 // Data15 ณ Data15 ณ Data15 ณ Data15 // ออออออออออุออออออออออุออออออออออุออออออออออ // Footing ณ Footing ณ Footing ณ Footing // -SubFoot ณ -SubFoot ณ -SubFoot ณ -SubFoot // // Be aware of the fact that this code accesses TBrowse internals // ie. TBrowse doesn't know anything about it. What the funcs are // doing is simply stuff() ColSep into the Heading and Footing area // of the virtual screen-string that is being maintained by TBrowse. // If you issue a TBrowse:configure() the whole beauty is gone because // TBrowse rebuildts the virtual screen itself. As a result, ColSep in // Heading/Footing will be overwritten with blanks (standard behaviour // of TBrowse) //********************************************************************** Func TBHeadColSep(oTBrowse) local cColSep :=oTBrowse:ColSep local nColSepLen :=len(cColSep) local nVScreenLen:=bin2i(substr(atail(oTbrowse),13)) local nVRowLen :=bin2i(substr(atail(oTbrowse),23)) local nHeadLines :=TBHeadRows( oTBrowse ) local cHeadLines :=substr(atail(oTbrowse),85,nVRowLen*nHeadLines) local cColInfo :=substr(atail(oTbrowse),85+nVScreenLen,12*oTBrowse:ColCount) local nColSepPos , i, j for j:=1 to nHeadLines for i:=1 to oTbrowse:ColCount-1 nColSepPos:=bin2i(substr(cColInfo,3+12*i))-nColSepLen+1 cHeadLines:=; stuff(cHeadLines,(nVRowLen*(j-1))+nColSepPos,nColSepLen,cColSep) next next cHeadLines := substr(atail(oTbrowse),1,84)+cHeadLines+; substr(atail(oTbrowse),85+len(cHeadLines)) oTbrowse[len(oTbrowse)]:=cHeadLines return oTBrowse ************************* Func TBFootColSep(oTBrowse) local cColSep :=oTBrowse:ColSep local nColSepLen :=len(cColSep) local nFootLines :=TBFootRows( oTBrowse ) local nVScreenLen:=TBScreenLen( oTBrowse ) local nVRowLen :=TBRowLen( oTBrowse ) local nStartBlock:=bin2i(substr(atail(oTbrowse),45)) local cFootLines :=substr(atail(oTbrowse),85+nStartBlock*nVRowLen) local cColInfo :=substr(atail(oTbrowse),85+nVScreenLen,12*oTBrowse:ColCount) local nColSepPos := 0 , i, j for j:=1 to nFootLines for i:=1 to oTbrowse:ColCount-1 nColSepPos:=bin2i(substr(cColInfo,3+12*i))-nColSepLen+1 cFootLines:=; stuff(cFootLines,(nVRowLen*(j-1))+nColSepPos,nColSepLen,cColSep) next next cFootLines := substr(atail(oTbrowse),1,84+nStartBlock*nVRowLen)+cFootLines oTbrowse[len(oTbrowse)]:=cFootLines return oTBrowse