home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.alaska-software.com
/
2014.06.ftp.alaska-software.com.tar
/
ftp.alaska-software.com
/
documents
/
RecordSet.prg
< prev
next >
Wrap
Text File
|
2003-01-16
|
9KB
|
318 lines
/////////////////////////////////////////////////////////////////////////////
//
// RecordSet.prg
//
// Copyright:
// Alaska Software, (c) 2003. All rights reserved.
//
// Contents:
// The RecordSet class is a Meta class. It creates classes for accessing
// columns of a 2-dimensional array via symbolic names
//
// Remarks:
// Procedure Main demonstrates how to take advantage of a Meta class.
//
// Two dynamic classes are created for displaying the Direcory() array:
// - the first class manages the Directory() array
// - the second class manages data required to create a text-mode browser
//
/////////////////////////////////////////////////////////////////////////////
#include "Inkey.ch"
#include "class.ch"
PROCEDURE Main
LOCAL nKey
LOCAL aRecords, aColumnNames
LOCAL oClass , oRecordSet, oBrowse , oColumn, oColumnData
CLS
/*
* 1. Create a class for accessing the Directory() array
*/
aColumnNames := { "FILENAME" , "FILESIZE", "WRITEDATE" , "WRITETIME", ;
"ATTRIBUTE", "EXTENDED", "CREATEDATE","CREATETIME", ;
"ACCESSDATE", "ACCESSTIME" }
oClass := RecordSet():createClass( "Dir", aColumnNames )
oRecordSet := oClass:new( Directory() )
/*
* 2. Create a class holding data for TBrowse/TBColumn creation
*/
aColumnNames := { "COLUMNNAME", "HEADING", "BLOCK", "WIDTH" }
oClass := RecordSet():createClass( "ColumnData", aColumnNames )
aRecords := { ;
{ "FILENAME" , "File Name", {|| oRecordSet:FILENAME }, 35 }, ;
{ "FILESIZE" , "File Size", {|| oRecordSet:FILESIZE }, 10 }, ;
{ "ATTRIBUTE" , "Attrib" , {|| oRecordSet:ATTRIBUTE }, 6 }, ;
{ "ACCESSDATE", "Last Date", {|| oRecordSet:ACCESSDATE }, 12 }, ;
{ "ACCESSTIME", "Last Time", {|| oRecordSet:ACCESSTIME }, 12 } ;
}
oColumnData := oClass:new( aRecords )
/*
* Build the TBrowse using navigational methods of the oRecordSet object
* that navigates through the Directory() array
*/
oBrowse := TBrowse():new( 1,1,25,78 )
oBrowse:goTopBlock := {|| oRecordSet:goTop() }
oBrowse:goBottomBlock := {|| oRecordSet:goBottom() }
oBrowse:skipBlock := {|n| oRecordSet:skipper(n) }
/*
* Add columns to the browser using data managed by the
* oColumnData object. The browser displays only 5 of 10 columns
* of the Directory() array. The numeric position of the displayed
* column is not relevant, since it is identified by symbolic name
* stored in oColumnData:block.
*/
DO WHILE .NOT. oColumnData:eof()
oColumn := TBColumn():new( oColumnData:heading, ;
oColumnData:block )
oColumn:width := oColumnData:width
oBrowse:addColumn( oColumn )
oColumnData:skip()
ENDDO
/*
* Display the Directory() array
*/
nKey := 0
DO WHILE nKey <> K_ESC
oBrowse:forceStable()
nKey := Inkey(0)
TBApplyKey( oBrowse, nKey )
/*
* When Enter is pressed, display data sorted by
* the current column
*/
IF Lastkey() == K_ENTER
/*
* oBrowse:colPos is the current browser column
* Navigate to the equivalent row of the column data array
*/
oColumnData:goTo( oBrowse:colPos )
/*
* Sort the Directory() array by column name and refresh browser
*/
oRecordSet:sort( oColumnData:COLUMNNAME )
oBrowse:refreshAll()
ENDIF
ENDDO
RETURN
/*
* Class for accessing 2-dim arrays
*/
CLASS RecordSet
PROTECTED:
CLASS VAR columnNames // Names of the array columns
VAR bof // Logical flag for BoF
VAR eof // Logical flag for EoF
VAR index // Array holding sort order
INLINE METHOD resetFlags
::bof := .F.
::eof := .F.
RETURN self
EXPORTED:
CLASS METHOD createClass
METHOD skipper
METHOD sort
VAR records READONLY // The 2-dim data array
VAR recno READONLY // Pointer to current row
VAR lastrec READONLY // Total number of rows
INLINE CLASS METHOD initClass( aColumnNames )
IF Valtype( aColumnNames ) == "A"
::columnNames := AClone( aColumnNames )
ENDIF
RETURN self
INLINE METHOD init( aRecords )
::resetFlags()
::records := aRecords
::recno := 1
::lastrec := Len( aRecords )
::index := Array( ::lastrec )
// Initial sort order is the natural/original order
AEval( ::index, {|n,i| n:=i },,, .T. )
RETURN self
INLINE METHOD getVar( nColumn )
IF ::lastrec == 0
RETURN NIL
ENDIF
RETURN ::records[ ::index[ ::recno ], nColumn ]
INLINE METHOD putVar( nColumn, xValue )
IF ::lastrec == 0
RETURN NIL
ENDIF
RETURN ::records[ ::index[ ::recno ], nColumn ] := xValue
INLINE METHOD bof
RETURN ::bof
INLINE METHOD eof
RETURN ::eof
// Navigate the row pointer for the array.
// NOTE: There is no "ghost record" as for database files
INLINE METHOD skip( n )
IF n == NIL
n := 1
ENDIF
::recno += n
::resetFlags()
IF ::recno < 1
::bof := .T.
::recno := 1
ENDIF
IF ::recno > ::lastrec
::eof := .T.
::recno := ::lastrec
ENDIF
RETURN self
INLINE METHOD goTo( nRecno )
::skip( nRecno - ::recno )
RETURN self
INLINE METHOD goTop
::resetFlags()
::recno := 1
RETURN self
INLINE METHOD goBottom
::resetFlags()
::recno := ::lastRec
RETURN self
ENDCLASS
/*
* Create a new class for accessing a 2-dim array of known columns
*/
CLASS METHOD RecordSet:createClass( cClassname, aColumnNames )
LOCAL oClass := ClassObject( cClassName )
LOCAL i, imax:= Len( aColumnNames )
LOCAL aMethod, cBlock, cName, nType
IF oClass <> NIL
// Class object exists already
RETURN oClass
ENDIF
// Instance variables are in fact EXPORTED ACCESS/ASSIGN methods
nType := CLASS_EXPORTED + METHOD_INSTANCE + ;
METHOD_ACCESS + METHOD_ASSIGN
// Class does not exist yet
aMethod:= Array( imax )
FOR i:=1 TO imax
// Name of iVar
cName := aColumnNames[i]
// Each iVar is mapped to the generic :getVar()/:putVar() methods.
// Both receive the numeric column index i
cBlock := "{|o,x| IIf(x==NIL," + ;
"o:getVar(" + Var2Char(i) + ")," + ;
"o:putVar(" + Var2Char(i) + ",x))}"
aMethod[i] := { cName, nType, &(cBlock), cName }
NEXT
// Create the new class object and use RecordSet as super class (=self).
// This way, the derived new class knows the :getVar()/:putVar()
// and navigational methods
oClass := ClassCreate( cClassName, { self }, {}, aMethod )
// Initialize the new class object
oClass:initClass( aColumnNames )
RETURN oClass
/*
* Method to be used by a browser for navigating
* the row pointer of a 2-dim array
*/
METHOD RecordSet:skipper( nWantSkip )
LOCAL nDidSkip := 0
DO CASE
CASE ::lastrec == 0 .OR. nWantSkip == 0
::skip(0)
CASE nWantSkip > 0
DO WHILE nDidSkip < nWantSkip
::skip(1)
IF ::eof
EXIT
ENDIF
nDidSkip ++
ENDDO
CASE nWantSkip < 0
DO WHILE nDidSkip > nWantSkip
::skip(-1)
IF ::bof
EXIT
ENDIF
nDidSkip --
ENDDO
ENDCASE
RETURN nDidSkip
/*
* Sort the ::index array, not the data array referenced in ::records
* The ::index array holds numeric row pointers.
* Sorting the ::index array leaves the original data array intact!
*/
METHOD RecordSet:sort( nColumn )
IF nColumn == NIL
nColumn := 0
ENDIF
IF Valtype( nColumn ) == "C"
nColumn := AScan( ::columnNames, {|c| Upper(c) == Upper(nColumn) } )
ENDIF
IF nColumn == 0
AEval( ::index, {|n,i| n:=i },,, .T. )
ELSE
AASort( ::index, ::records, nColumn )
ENDIF
RETURN self
STATIC PROCEDURE AASort( aIndex, aRecords, nColumn )
ASort( aIndex, , ,{|n1,n2| aRecords[n1,nColumn] < aRecords[n2,nColumn] } )
RETURN