#include "FiveWin.ch"
REQUEST DbfCdx
FUNCTION Main()
LOCAL oDlg, oBrw
// 1. Database Creation Checks
IF !File( "tasks.dbf" )
DbCreate( "tasks.dbf", { ;
{ "ID", "+", 10, 0 }, ;
{ "TASK", "C", 50, 0 } } )
ENDIF
IF !File( "details.dbf" )
DbCreate( "details.dbf", { ;
{ "ID", "N", 10, 0 }, ;
{ "TASK_ID", "N", 10, 0 },;
{ "DATE", "D", 8, 0 },;
{ "FROM", "C", 50, 0 },;
{ "TO", "C", 50, 0 } } )
ENDIF
// 2. Open Databases and Indexes
USE tasks VIA "DBFCDX"
IF Tasks->( RecCount() ) == 0
Tasks->( DbAppend() )
Tasks->Task := "Project Alpha"
ENDIF
INDEX ON field->ID TO "ID"
SET ORDER TO TAG ID
USE details VIA "DBFCDX" NEW
IF Details->( RecCount() ) == 0
Details->( DbAppend() )
Details->Task_id := Tasks->Id
Details->Date := Date()
Details->From := "09:00"
Details->To := "11:00"
ENDIF
INDEX ON field->Task_id TO "TASK_ID"
SET ORDER TO TAG TASK_ID
SELECT "tasks"
SET RELATION TO details->Task_id INTO details
// 3. UI Setup
DEFINE DIALOG oDlg SIZE 1200, 400 PIXEL TRUEPIXEL
@ 2.5, 2 XBROWSE oBrw SIZE 1170, 350 OF oDlg ;
DATASOURCE "Tasks" ;
CELL LINES NOBORDER
oBrw:CreateFromCode()
oBrw:SetTree( BuildTree() )
oBrw:aCols[ 1 ]:nWidth := 500
// Edit action
oBrw:bEdit = { | o | EditItem( o:oBrw:oTreeItem, o:oBrw ) }
// Navigation action (Sync DB cursors)
oBrw:bChange = { | o | If( Len( o:oTreeItem:Cargo ) == 2,;
Tasks->( DbGoTo( o:oTreeItem:Cargo[ 2 ] ) ),;
Details->( DbGoTo( o:oTreeItem:Cargo[ 2 ] ) ) ) }
oDlg:oClient = oBrw
ACTIVATE DIALOG oDlg CENTERED ;
ON INIT BuildBar( oDlg, oBrw )
RETURN NIL
//----------------------------------------------------------------------------//
FUNCTION BuildBar( oDlg, oBrw )
LOCAL oBar
DEFINE BUTTONBAR oBar OF oDlg SIZE 50, 50 2007
// Modified: Call static functions instead of inline code blocks
DEFINE BUTTON OF oBar PROMPT "Task" RESOURCE "add" ;
ACTION AddTask( oBrw )
DEFINE BUTTON OF oBar PROMPT "Detail" RESOURCE "add" ;
ACTION AddDetail( oBrw )
DEFINE BUTTON OF oBar PROMPT "Edit" RESOURCE "edit" ACTION oBrw:Edit()
DEFINE BUTTON OF oBar PROMPT "Del" RESOURCE "del"
oDlg:Resize()
RETURN .T.
//----------------------------------------------------------------------------//
FUNCTION BuildTree()
LOCAL oTree, nRecNo := Tasks->( RecNo() )
IF Tasks->( RecCount() ) > 0
TREE oTree
Details->( DbGoTop() )
Tasks->( DbGoTop() )
WHILE !Tasks->( Eof() )
// Cargo format: { "task", TaskRecNo }
TREEITEM tasks->Task CARGO { "task", Tasks->( RecNo() ) }
IF Details->( RecCount() ) > 0
TREE
WHILE Details->Task_id == tasks->id .and. !Details->( Eof() )
// Cargo format: { "detail", DetailRecNo, TaskRecNo }
TREEITEM "Date: " + DToC( Details->Date ) + ;
" - From: " + RTRim( Details->From ) + ;
" - To: " + RTrim( Details->To ) ;
CARGO { "detail", Details->( RecNo() ), Tasks->( RecNo() ) }
Details->( DbSkip() )
END
ENDTREE
ENDIF
Tasks->( DbSkip() )
END
ENDTREE
Tasks->( DbGoTo( nRecNo ) )
ELSE
TREE oTree
ENDTREE
ENDIF
RETURN oTree
//----------------------------------------------------------------------------//
FUNCTION EditItem( oTreeItem, oBrw )
LOCAL nRec := oTreeItem:Cargo[ 2 ], oDlg
LOCAL nBrwPos := oBrw:nRowSel
IF oTreeItem:Cargo[ 1 ] == "detail"
DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit detail"
Details->( DbGoTo( nRec ) )
@ 1, 2 SAY "Date:" OF oDlg
@ 1, 6 GET Details->Date PICTURE "9999-99-99" OF oDlg
@ 3.2, 2 SAY "From:" OF oDlg
@ 3.5, 6 GET Details->From PICTURE "99:99" OF oDlg
@ 5.3, 2 SAY "To:" OF oDlg
@ 5.9, 6 GET Details->To PICTURE "99:99" OF oDlg
@ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
ACTION ( Details->( DbCommit() ), oDlg:End(),;
UpdateTree( oBrw, nBrwPos ) ) // Modified: Use UpdateTree helper
@ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()
ACTIVATE DIALOG oDlg CENTERED
ELSE
DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit task"
Tasks->( DbGoTo( nRec ) )
@ 1, 2 SAY "Task:" OF oDlg
@ 1, 6 GET Tasks->Task SIZE 340, 20 OF oDlg
@ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
ACTION ( Tasks->( DbCommit() ), oDlg:End(),;
UpdateTree( oBrw, nBrwPos ) ) // Modified: Use UpdateTree helper
@ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()
ACTIVATE DIALOG oDlg CENTERED
ENDIF
RETURN NIL
//----------------------------------------------------------------------------//
// NEW HELPER FUNCTIONS FOR TREE STATE MANAGEMENT
//----------------------------------------------------------------------------//
// Action to add a new Task
STATIC FUNCTION AddTask( oBrw )
LOCAL aState
// 1. Save current state
aState := SaveTreeState( oBrw:oTree )
// 2. Database logic
Tasks->( DbAppend() )
Details->( DbAppend() )
Details->Task_id := Tasks->Id
Details->Date := Date()
Details->From := "09:00"
Details->To := "11:00"
// 3. Rebuild the tree
oBrw:oTree := BuildTree()
// 4. Restore what was previously open
RestoreTreeState( oBrw:oTree, aState )
oBrw:Refresh()
RETURN NIL
// Action to add a new Detail
STATIC FUNCTION AddDetail( oBrw )
LOCAL aState
LOCAL nTaskRec
// Check if there are items before trying to read Cargo
IF oBrw:oTreeItem == NIL; RETURN NIL; ENDIF
// Get Parent Task RecNo (regardless of current position)
nTaskRec := If( Len( oBrw:oTreeItem:Cargo ) == 2,;
oBrw:oTreeItem:Cargo[ 2 ],;
oBrw:oTreeItem:Cargo[ 3 ] )
// 1. Save current state
aState := SaveTreeState( oBrw:oTree )
// --- TRICK ---
// Force the parent task to be Open (.T.) in the state array
ForceParentOpen( aState, nTaskRec )
// -------------
// 2. Database logic
Tasks->( DbGoTo( nTaskRec ) )
Details->( DbAppend() )
Details->task_id := Tasks->id
Details->Date := Date()
Details->From := "09:00"
Details->To := "11:00"
// 3. Rebuild tree
oBrw:oTree := BuildTree()
// 4. Restore (now includes our forced open task)
RestoreTreeState( oBrw:oTree, aState )
oBrw:Refresh()
RETURN NIL
// Helper to update tree without collapsing everything (Used in EditItem)
FUNCTION UpdateTree( oBrw, nBrwPos )
LOCAL aState
// 1. Save state
aState := SaveTreeState( oBrw:oTree )
// 2. Rebuild
oBrw:oTree := BuildTree()
// 3. Restore
RestoreTreeState( oBrw:oTree, aState )
// 4. Restore cursor position and refresh
oBrw:nRowSel := nBrwPos
oBrw:Refresh()
RETURN NIL
// Helper to ensure the task where we are adding a detail is open
STATIC FUNCTION ForceParentOpen( aState, nTaskRec )
LOCAL nAt
// Search in state array if that specific Task exists
nAt := AScan( aState, { | a | a[ 2 ][ 1 ] == "task" .and. a[ 2 ][ 2 ] == nTaskRec } )
IF nAt > 0
// If exists, force it to be Open (.T.)
aState[ nAt ][ 1 ] := .T.
ELSE
// If not found, add it forced as open
AAdd( aState, { .T., { "task", nTaskRec } } )
ENDIF
RETURN NIL
// Function to save the current status (Open/Closed) of the tree items
FUNCTION SaveTreeState( oTree )
LOCAL aState := {}
LOCAL oItem
oItem := oTree:oFirst
WHILE oItem != NIL
// Save { IsOpen, CargoData }
AADD( aState, { oItem:lOpened, oItem:Cargo } )
oItem := oItem:Skip( 1 )
END
RETURN aState
// Function to restore the status (Open/Closed) to a new tree
FUNCTION RestoreTreeState( oTree, aState )
LOCAL oItem := oTree:oFirst
LOCAL nAt
WHILE oItem != NIL
// Search if this item's 'Cargo' exists in our saved state array
// Cargo structure: [1] = Type ("task"/"detail"), [2] = ID
nAt := AScan( aState, { | a | ValType( a[ 2 ] ) == "A" .and. ;
ValType( oItem:Cargo ) == "A" .and. ;
a[ 2 ][ 1 ] == oItem:Cargo[ 1 ] .and. ;
a[ 2 ][ 2 ] == oItem:Cargo[ 2 ] } )
// If found and it was marked as opened (.T.), we open it in the new tree
IF nAt > 0 .and. aState[ nAt ][ 1 ]
oItem:Open()
ENDIF
// Move to next. Calling Open() above makes Skip(1) enter the children automatically
oItem := oItem:Skip( 1 )
END
RETURN NIL