unit tree_unit;
{ DavData software
www.davdata.nl
tree operations on elements with undo
version 5, sept. 2013
- separated element links (local) and properties (global)
by using different units for links and data
local array Links[1..maxElement] with fields:
- parent
- child
- next
- previous
parent field bits 24,25,26,27
hold element status: 0000 = free
0001 = active
0010 = deleted
0011 = replaced
0100 = unpacked
operations description
---------- --------------
placeA place after
placeB place before
placeChildA place as last child
placeChildB place as first child
delete de-activate
replace replace
moveA move element after
moveB move element before
moveChildA move element after last child
moveChildB move element as first children
packEl pack element (place as child)
unpackEl unpack element (remove parent)
navigating
----------
getFreeEl get index of free element
parentEL get parent
ChildEL get child
NextEL get next
PreviousEL get previous
scanElement supply neighbour element in tree:
old relation new direction search in priority order
------------- --------------------------------------
next,child child,next,previous,parent
previous previous, parent
parent next,previous,parent
status and statistics
---------------------
getELstatus return status of element : stFree,stActive,stDeleted,
stReplaced
getTreeStats return statistics
undo
----
undoAction undo last operation
purgeUndoStack remove undostack, destroy deleted elements
startlinking next action is linked
stoplinking next action is not linked
global constants
----------------
maxelement total number of elements 1..maxelement
maxUndo total number of undo steps remembered 1..maxUndo
local (protected) variables
---------------------------
Links[1..maxElement]
undolist[1..maxUndo] array of undo steps
}
interface
uses windows;
const maxelement = 50;
maxundo = 25; //undo actions
type TLinkType = (ltNone,ltParent,ltChild,ltNext,ltPrevious);
TElementStatus = (esFree,esActive,esDeleted,esReplaced,esUnpacked);
TELaction = (eaPlaceA,eaPlaceB,eaPlaceChildA,eaPlaceChildB,eaReplace,
eaDelete,eaMoveA,eaMoveB,eaMoveChildA,eaMoveChildB,
eaPack,eaUnpack,eaUndo,eaNone);
TLinks = record
parent : dword; //bits 24,25,26 hold status of element
child : dword;
next : dword;
previous : dword;
end;
TEditAction = record
action : TELaction;
elmnt : dword; //element
linkEl : dword; //link element
linktype: TLinkType;
cc : dword; //child count for UnpackEl operation
bwlink : boolean; //backward link
end;
TTreeStats = record
freeCount : dword; //# free elements
freeNr : dword; //free element
lastAction : word;
end;
//-- initialize
procedure initTree;
//--edit procedures
function getFreeEl(var el : dword) : boolean;//get free element number
procedure PlaceA(del,sel : dword); //place element sel after del
procedure PlaceB(del,sel : dword); //place element sel after del
procedure PlaceChildA(del,sel : dword); //place elemnt sel after child
procedure PlaceChildB(del,sel : dword); //place element sel before child
procedure ReplaceEL(del,sel : dword); //replace element del by sel
procedure deleteEL(del : dword); //delete element el
procedure moveElA(del,sel : dword);
procedure moveElB(del,sel : dword);
procedure moveELChildA(del,sel : dword);
procedure moveElChildB(del,sel : dword);
procedure packEl(del,sel : dword);
procedure unpackEl(sel : dword);
//--navigating
function ParentEl(var nel : dword) : boolean;
function ChildEl(var nel : dword) : boolean;
function NextEl(var nel : dword) : boolean;
function previousEl(var nel : dword) : boolean;
function ScanElement(var elmnt : dword; lt : TLinkType) : TLinktype;
function getChildCount(del : dword) : dword;
function getLastChild(del : dword) : dword;
//--status
function GetElLinks(n : dword) : TLinks;
function getELStatus(el : dword) : TElementStatus;
function getTreeStatistics : TTreestats;
function getstackdata(n : word) : TEditAction;
//-- undo stack
procedure startLinking;
procedure stoplinking;
procedure purgeUndoStack; //clear all undo information
procedure UndoAction; //undo last action
//--set properties --- custom procedures
implementation
var Links : array[1..maxelement] of TLinks;
startflag,linkflag : boolean;
UndoStack : array[1..maxundo] of TEditAction;
TreeStats : TTreeStats;
procedure destroyEL(del : dword);forward;
//--- initialisation
procedure clearElement(el : dword);
begin
with Links[el] do
begin
parent := 0; //also free
child := 0;
next := 0;
previous := 0;
end;
end;
procedure clearUndoStack(nr : word);
//clear entry nr in undo stack
begin
with UndoStack[nr] do
begin
action := eaNone;
elmnt := 0;
linkEl := 0;
linkType := ltNone;
cc := 0;
bwlink := false;
end;
end;
procedure setElStatus(elnr : dword; st : TElementStatus);
//set status of element elnr to st
var dw : dword;
begin
dw := Links[elNr].parent and $ffffff;
Links[elNr].parent := dw or (byte(st) shl 24);
end;
procedure clearEditAction(var ea : TEditAction);
//clear some fields of Edit Action record
begin
with ea do
begin
linkEl := 0;
linktype := ltNone;
bwlink := false;
end;
end;
procedure initTree;
//clear all elements
//set element 1 = active
//clear undostack
var i : dword;
begin
for i := 1 to maxelement do clearElement(i);
with treestats do
begin
freeNr := 2;
freecount := maxelement-1;
lastAction := 0;
end;
setELstatus(1,esActive);
for i := 1 to maxUndo do clearUndoStack(i);
startflag := false;
linkflag := false;
end;
//--- status
function getELStatus(el : dword) : TElementStatus;
//element#1 always active
begin
result:= TElementstatus(Links[el].parent shr 24);
end;
function getTreeStatistics : TTreestats;
//return tree statistics
begin
result := treestats;
end;
function getstackdata(n : word) : TEditAction;
//return stack data
begin
result := undoStack[n];
end;
function GetElLinks(n : dword) : TLinks;
//return element data
begin
result := Links[n];
result.parent := result.parent and $ffffff;
end;
//--- protected operations
procedure destroyEl(del : dword);
//destroy replaced,unpacked element del
//do not touch children
begin
clearElement(del);
inc(treestats.freeCount);
end;
procedure destroyELch(del : dword);
//destroy deleted element and it's children
//adjust freeElementcount
var mcode : TLinktype;
oldEl : dword;
begin
with Links[del] do
begin
parent := 0; //isolate element, keep link to children
next := 0;
previous := 0;
end;
mcode := ltChild;
repeat
OldEl := del;
mcode := ScanElement(del,mcode);
if (mcode = ltPrevious) or (mcode = ltParent) or (mcode = ltNone) then
begin
clearElement(oldEl);
inc(treestats.freecount);
end;
until mcode = ltNone;
end;
function getLink(el : dword) : TEditAction;
//set linkEl , linkType information
begin
with Links[el] do
if previous > 0 then
begin
result.linkEl := previous;
result.linktype := ltPrevious;
end
else begin
result.linkEl := parent and $ffffff;
result.linktype := ltParent;
end;
end;
function pickEL(pel : dword) : TEditAction;
//extract element pel from tree, repair tree
var par,chd,nxt,prv : dword;
begin
with result do
begin
with Links[pel] do
begin
par := parent and $ffffff;
chd := child;
nxt := next;
prv := previous;
end;
if prv > 0 then Links[prv].next := nxt
else if par > 0 then
begin
Links[par].child := nxt;
linkType := ltParent;
linkEL := par;
end;
if nxt > 0 then Links[nxt].previous := prv;
end;//with result
end;
procedure LinkIn(del : dword);
//element del has correct links but it's parent,children,next,previous
//must be adjusted to point to del
var par,chd,nxt,prv : dword;
begin
with Links[del] do
begin
par := parent and $ffffff;
chd := child;
nxt := next;
prv := previous;
end;
if nxt > 0 then Links[nxt].previous := del;
if prv > 0 then Links[prv].next := del
else Links[par].child := del;
if chd > 0 then
repeat
Links[chd].parent := del or $1000000;//set active
until nextEl(chd) = false;
end;
procedure copyLinks(var del,sel : dword);
begin
with Links[del] do
begin
parent := Links[sel].parent and $ffffff;
child := Links[sel].child;
next := lInks[sel].next;
previous := Links[sel].previous;
end;
end;
procedure UN_delete(const ea : TEditAction);
//un delete element
var elmnt : dword;
begin
elmnt := ea.elmnt;
LinkIn(elmnt);
setElStatus(elmnt,esActive);
end;
procedure UN_replace(const ea : TEditAction);
//re-replace element el
//var actel,par,nxt,prv : dword;
begin
with ea do
begin
LinkIn(elmnt);
setElStatus(elmnt,esActive);
destroyEl(linkEl);
end;
end;
procedure UN_Pack(const ea : TEditAction);
//undo a Pack operation
var par,chd : dword;
begin
par := ea.elmnt;
chd := Links[par].child;
Links[chd].parent := Links[par].parent;
Links[chd].previous := Links[par].previous;
Links[chd].next := Links[par].next;
LinkIn(chd);
destroyEL(par);
end;
procedure UN_Unpack(const ea: TEditAction);
//undo an UnPack operation = RePack
var chd,par,nxt,count : dword;
begin
par := ea.elmnt;
LinkIn(par);
setElStatus(par,esActive);
chd := Links[par].child;
Links[chd].previous := 0;
for count := 1 to ea.cc-1 do nextEl(chd);
nxt := chd;
if nextEL(nxt) then Links[nxt].previous := par;
Links[chd].next := 0;
end;
procedure UN_MoveEl(const ea : TEditAction);
//undo moveA,moveB,moveChildA,moveChildB
begin
pickEL(ea.elmnt);
with Links[ea.elmnt] do
case ea.linktype of
ltParent : begin
parent := ea.linkEl;
previous := 0;
next := Links[parent].child;
end;
ltprevious : begin
parent := Links[ea.linkEL].parent;
previous := ea.linkEl;
next := Links[previous].next;
end;
end;//case
LinkIn(ea.elmnt);
setELstatus(ea.elmnt,esActive);
end;
procedure ShiftStack;
//shift stack up 1 place or more if linked
var link : boolean;
i : word;
begin
if treestats.lastAction = 0 then exit;
repeat
with undoStack[1] do
case action of
eaReplace,
eaUnpack : destroyEl(elmnt); //element only
eaDelete : destroyElch(elmnt); //children as well
end;//case
if treestats.lastaction > 1 then link := undoStack[2].bwlink;
for i := 1 to treestats.lastAction - 1 do undoStack[i] := undoStack[i+1];
clearUndoStack(treestats.lastaction);
dec(treestats.lastaction);
until link = false;
end;
procedure purgeUndoStack;
//undo all actions
begin
while treestats.lastaction > 0 do ShiftStack;
end;
procedure registerAction(act : TEditAction);
//add link flag to action, register in undo list
begin
if treestats.lastAction = maxUndo then ShiftStack;
inc(treestats.lastAction);
act.bwlink := linkflag;
undoStack[treestats.lastaction] := act;
linkflag := startflag;
end;
//--- navigation
function ParentEl(var nel : dword) : boolean;
var e : dword;
begin
e := links[nel].parent and $ffffff;
result := e > 0;
if result then nel := e;
end;
function ChildEl(var nel : dword) : boolean;
var e : dword;
begin
e := Links[nel].child;
result := e > 0;
if result then nel := e;
end;
function NextEl(var nel : dword) : boolean;
var e : dword;
begin
e := Links[nel].next;
result := e > 0;
if result then nel := e;
end;
function previousEl(var nel : dword) : boolean;
var e : dword;
begin
e := Links[nel].previous;
result := e > 0;
if result then nel := e;
end;
function getChildCount(del : dword) : dword;
//return number of children
begin
result := 0;
if childEl(del) then
begin
inc(result);
while nextEl(del) do inc(result);
end;
end;
function getLastChild(del : dword) : dword;
//return 0 if no child
begin
result := 0;
if childEl(del) then
begin
repeat until nextEl(del) = false;
result := del;
end;
end;
//--- undo operations
procedure startLinking;
//start new edit commands
//clear linkflag, set startflag
begin
linkflag := false;
startFlag := true;
end;
procedure stopLinking;
begin
startflag := false;
linkflag := false;
end;
procedure UndoAction;
var par,prv,nxt : dword;
bwl : boolean;
ea : TEditAction;
begin
repeat
ea := UndoStack[treestats.lastAction];
begin
bwl := ea.bwLink;
case ea.action of
eaPlaceA,
eaPlaceB,
eaPlaceChildA,
eaPlaceChildB : begin
pickEl(ea.elmnt);
destroyEl(ea.elmnt);
end;
eaDelete : UN_delete(ea);
eaReplace : UN_replace(ea);
eaPack : UN_Pack(ea);
eaUnPack : UN_Unpack(ea);
eaMoveA,
eaMoveB,
eaMoveChildA,
eaMoveChildB : UN_moveEL(ea);
end;
end;//with undolist
dec(treestats.lastaction);
until bwl = false;
end;
//--- element actions ---
function getFreeEl(var el : dword) : boolean;
//return free element number elnr
//return true if Ok, false if out of elements
var n : DWORD;
begin
n := treestats.freeNr;
repeat
result := (Links[n].parent and $f000000) = 0;
if result then el := n
else begin
inc(n);
if n > maxelement then n := 1;
end;
until result or (n = treestats.freeNr);
if result then treestats.freeNr := n;
end;
procedure PlaceA(del,sel : dword);
//place free element sel after del
var nxt,par : DWORD;
ea : TEditAction;
begin
clearEditAction(ea);
ea.action := eaPlaceA;
ea.elmnt := sel;
with Links[del] do
begin
nxt := next;
par := parent;
end;
with Links[sel] do
begin
previous := del;
parent := par;
next := nxt;
end;
LinkIn(sel);
registerAction(ea);
dec(treeStats.freecount);
setElStatus(sel,esActive);
end;
procedure PlaceB(del,sel : dword);
//place free element sel before del
var prv,par : DWORD;
ea : TEditAction;
begin
clearEditAction(ea);
ea.action := eaPlaceB;
ea.elmnt := sel;
with Links[del] do
begin
prv := previous;
par := parent;
end;
with Links[sel] do
begin
previous := prv;
next := del;
parent := par;
end;
LinkIn(sel);
registerAction(ea);
dec(treeStats.freecount);
setElStatus(sel,esActive);
end;
procedure PlaceChildA(del,sel : dword);
//place free element sel as last child of del
var ea : TEditAction;
chd : dword;
begin
clearEditAction(ea);
ea.action := eaPlaceChildA;
ea.elmnt := sel;
chd := Links[del].child;
if chd > 0 then
repeat until nextEL(chd) = false; //position on last child
with Links[sel] do
begin
parent := del;
next := 0;
previous := chd;
end;
LinkIn(sel);
registeraction(ea);
dec(treeStats.freecount);
setElStatus(sel,esActive);
end;
procedure PlaceChildB(del,sel : dword);
//place free element sel as first child of del
var ea : TEditAction;
chd : dword;
begin
clearEditAction(ea);
ea.action := eaPlaceChildB;
ea.elmnt := sel;
chd := Links[del].child;
with Links[sel] do
begin
parent := del;
previous := 0;
next := chd;
end;
LinkIn(sel);
registerAction(ea);
dec(treeStats.freecount);
setElStatus(sel,esActive);
end;
procedure ReplaceEL(del,sel : dword);
//replace element del by sel
//sel must be free element
//del is part of tree
var nxt,prv,par,chd : dword;
ea : TeditAction;
begin
with ea do
begin
elmnt := del; //replaced element
action := eaReplace;
ea.linkEl := sel; //new element
end;
copyLinks(sel,del);
LinkIn(sel);
registerAction(ea);
dec(treeStats.freecount);
setElStatus(sel,esActive);
setElStatus(del,esReplaced);
end;
procedure deleteEL(del : dword);
//delete element del
//do not delete children
var ea : TEditAction;
begin
ea.action := eaDelete;
ea.elmnt := del;
pickEl(del);
registerAction(ea);
setElStatus(del,esDeleted);
end;
procedure packEl(del,sel : dword);
//free element sel becomes parent of del
var ea : TEditAction;
begin
with ea do
begin
action := eaPack;
elmnt := sel;
end;
copylinks(sel,del);
Links[sel].child := del;
LinkIn(sel);
with Links[del] do
begin
previous := 0;
next := 0;
end;
registerAction(ea);
dec(treeStats.freeCount);
setElStatus(sel,esActive);
end;
procedure unpackEl(sel : dword);
//remove active element sel, children not deleted
//if no children : NOP
var par,chd,nxt,prv : dword;
ea : TEditAction;
begin
if Links[sel].child = 0 then exit;
with ea do
begin
action := eaUnPack;
elmnt := sel;
cc := getChildCount(sel);
end;
with Links[sel] do
begin
par := parent and $ffffff;
chd := child;
nxt := next;
prv := previous;
end;
if prv > 0 then Links[prv].next := chd
else if par > 0 then Links[par].child := chd;
Links[chd].previous := prv;
repeat
Links[chd].parent := par or $1000000;//set active
until nextEl(chd) = false;
Links[chd].next := nxt;
if nxt > 0 then Links[nxt].previous := chd;
setElStatus(sel,esUnpacked);
registerAction(ea);
end;
procedure moveElA(del,sel : dword);
//move active element sel after del
var ea : TEditAction;
par,nxt : dword;
begin
ea := getLink(sel);
with ea do
begin
action := eaMoveA;
elmnt := sel;
end;
pickEl(sel);
with Links[del] do
begin
par := parent and $ffffff;
nxt := next;
end;
with Links[sel] do
begin
previous := del;
next := nxt;
parent := par;
end;
setELstatus(sel,esActive);
LinkIn(sel);
registerAction(ea);
end;
procedure moveElB(del,sel : dword);
//move active element sel after del
var ea : TEditAction;
par, prv : dword;
begin
ea := getLink(sel);
with ea do
begin
action := eaMoveB;
elmnt := sel;
end;
pickEL(sel);
with Links[del] do
begin
par := parent and $ffffff;
prv := previous;
end;
with Links[sel] do
begin
parent := par;
next := del;
previous := prv;
end;
setELstatus(sel,esActive);
LinkIn(sel);
registerAction(ea);
end;
procedure moveELChildA(del,sel : dword);
//move active element sel after last child of del
var ea : TEditAction;
chd : dword;
begin
ea := getLink(sel);
with ea do
begin
action := eaMoveChildA;
elmnt := sel;
end;
pickEL(sel);
chd := Links[del].child;
if chd > 0 then repeat until nextEL(chd) = false;
with Links[sel] do
begin
parent := del;
next := 0;
previous := chd;
end;
setELstatus(sel,esActive);
LinkIn(sel);
registerAction(ea);
end;
procedure moveElChildB(del,sel : dword);
//move active element sel to first child of del
var ea : TEditAction;
chd : dword;
begin
ea := getLink(sel);
with ea do
begin
action := eaMoveChildA;
elmnt := sel;
end;
pickEL(sel);
chd := Links[del].child;
with Links[sel] do
begin
parent := del;
next := chd;
previous := 0;
end;
setELstatus(sel,esActive);
LinkIn(sel);
registerAction(ea);
end;
function ScanElement(var elmnt : dword; lt : TLinktype) : TLinktype;
//walk thru tree, children first then next (down)
//replace elmnt by next element
begin
result := ltNone;
if (lt = ltChild) or (lt = ltNext) then
begin
if childEl(elmnt) then result := ltChild
else if nextEl(elmnt) then result := ltNext
else if previousEl(elmnt) then result := ltPrevious
else if parentEl(elmnt) then result := ltParent;
end;
if result <> ltNone then exit;
if lt = ltPrevious then
begin
if previousEl(elmnt) then result := ltPrevious
else if parentEl(elmnt) then result := ltParent;
end;
if result <> ltNone then exit;
if lt = ltParent then
begin
if nextEl(elmnt) then result := ltNext
else if previousEl(elmnt) then result := ltPrevious
else if parentEl(elmnt) then result:= ltParent;
end;
end;
end.