|
 |
Listing of Delphi tree_unit |
 |
 |
terug naar artikel
unit tree_unit;
{ supply tree operations on elements with undo
maxelement = number of elements
maxaction = number of undo steps saved
element is record with mandatory fields:
- parent
- child
- next
- previous
- elType (eltFree = element not in use)
- + customfields like x,y position, width, height, font, code.......
operation description undo action stack purge action
--------- -------------- ----------- ------------------
insertA insert after destroy none
insertB insert before destroy none
addChild insert child destroy none
delete but save restore destroy
replace but save old reset destroy
pack add to previous unpack none
destroy old
delete : deactivate element, but do not destroy
undo re-activetes
destroy : permanently remove element
}
interface
uses windows;
type TElmntAction = (eaNone,eaInsertA,eaInsertB,eaDelete,eaReplace,
eaAddChild,eaPack);
TTreemove = (tmNone,tmParent,tmChild,tmNext,tmPrevious);
TElementStatus = (esFree,esDeleted,esActive);
procedure initdata;
//--edit procedures
function getFreeElement(var el : dword) : boolean;
procedure insertElementA(el,new : dword); //insert after
procedure insertElementB(el,new : dword); //insert before
procedure replaceElement(el : dword; new : dword); //replace by
procedure addChildElement(el,new : dword);
procedure packElement(el : dword); //make child of previous
procedure deleteElement(el : dword); //delete element e
procedure UndoElement; //undo last action
procedure destroyElement(el : 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; tm : TTreeMove) : TTreeMove;
//--status
function getElementStatus(el : dword) : TElementStatus;
//--undo
procedure startLinking;
procedure stoplinking;
procedure registerAction(ea : TElmntAction; el : dword);
procedure purgeUndoStack;
const maxelement = 50;
maxaction = 25; //undo actions
type TElementtype = (elFree,elActive);
TElement = record
eltype : TElementType;
marked : boolean;
x,y : word;
parent : dword;
child : dword;
next : dword;
previous : dword
end;
TEditAction = record
elmnt : dword;
action : TelmntAction;
bwlink : boolean; //backward link
end;
var element : array[1..maxelement] of TElement;
undolist : array[1..maxaction] of TEditAction;
lastAction : word;
freeElementcount : dword = 0;
implementation
var freeElementNr : dword;
startflag,linkflag : boolean;
procedure clearElement(el : dword);
begin
with element[el] do
begin
parent := 0;
child := 0;
next := 0;
previous := 0;
elType := elFree;
marked := false;
x := 0;
y := 0;
end;
end;
procedure initdata;
var i : dword;
begin
for i := 1 to maxelement do clearElement(i);
freeElementNr := 1;
freeElementcount := maxelement;
for i := 1 to maxAction do
with undolist[i] do
begin
elmnt := 0;
action := eaNone;
bwlink := false;
end;
lastAction := 0;
with element[1] do
begin
x := 1;
y := 1;
end;
startflag := false;
linkflag := false;
end;
function getFreeElement(var el : dword) : boolean;
//return free element number el
//return true if Ok, false if out of elements
//decrement freeElementcount
var n : DWORD;
begin
n := freeElementNr;
repeat
result := element[n].elType = elFree;
if result then el := n
else begin
inc(n);
if n > maxelement then n := 1;
end;
until result or (n = freeElementNr);
if result then
begin
freeElementNr := n+1;
if freeElementNr > maxElement then freeElementNr := 1;
dec(freeElementcount);
with element[n] do //reset some element props
begin
child := 0;
next := 0;
end;
end;
end;
function getElementStatus(el : dword) : TElementStatus;
//esFree : no parent or previous
//esActive : connected with parent or previous
//esDeleted: not connected to parent or previous
//element 1 always active
begin
if el = 1 then
begin
result := esActive;
exit;
end;
with element[el] do
begin
if (parent = 0) and (previous = 0) then
begin
result := esFree;
exit;
end;
if parent > 0 then
if element[parent].child = el then
begin
result := esActive;
exit;
end;
if previous > 0 then
if element[previous].next = el then
begin
result:= esActive;
exit;
end;
end;
result := esDeleted;
end;
function ParentEl(var nel : dword) : boolean;
var e : dword;
begin
e := element[nel].parent;
if e > 0 then
begin
nel := e;
result := true;
end else result := false;
end;
function ChildEl(var nel : dword) : boolean;
var e : dword;
begin
e := element[nel].child;
if e > 0 then
begin
nel := e;
result := true;
end else result := false;
end;
function NextEl(var nel : dword) : boolean;
var e : dword;
begin
e := element[nel].next;
if e > 0 then
begin
nel := e;
result := true;
end else result := false;
end;
function previousEl(var nel : dword) : boolean;
var e : dword;
begin
e := element[nel].previous;
if e > 0 then
begin
nel := e;
result := true;
end else result := false;
end;
function ScanElement(var elmnt : dword; tm : TTreeMove) : TTreeMove;
//walk thru tree, children first then next (down)
//replace elmnt by next element
begin
result := tmNone;
if (tm = tmChild) or (tm = tmNext) then
begin
if childEl(elmnt) then result := tmChild
else if nextEl(elmnt) then result := tmNext
else if previousEl(elmnt) then result := tmPrevious
else if parentEl(elmnt) then result := tmParent;
end;
if result <> tmNone then exit;
if tm = tmPrevious then
begin
if previousEl(elmnt) then result := tmPrevious
else if parentEl(elmnt) then result := tmParent;
end;
if result <> tmNone then exit;
if tm = tmParent then
begin
if nextEl(elmnt) then result := tmNext
else if previousEl(elmnt) then result := tmPrevious
else if parentEl(elmnt) then result:= tmParent;
end;
end;
//--- element procedures
procedure deleteElement(el : dword);
//delete element el
//do not delete children
var prv,nxt,par : DWORD;
begin
with element[el] do
begin
nxt := next;
prv := previous;
par := parent;
if prv > 0 then element[prv].next := nxt
else
if par > 0 then element[par].child := nxt;
if nxt > 0 then element[nxt].previous := prv;
end;
registerAction(eaDelete,el);
end;
procedure insertElementA(el : dword; new : dword);
//insert new element after el
var nxt,par : DWORD;
begin
with element[el] do
begin
nxt := next;
par := parent;
end;
element[el].next := new;
with element[new] do
begin
previous := el;
parent := par;
next := nxt;
end;
if nxt > 0 then element[nxt].previous := new;
registerAction(eaInsertA,new);
end;
procedure insertElementB(el,new : dword);
//insert new element before e
var prv,par : DWORD;
begin
with element[el] do
begin
prv := previous;
par := parent;
end;
element[el].previous := new;
with element[new] do
begin
previous := prv;
if previous = 0 then element[par].child := new
else element[prv].next := new;
next := el;
parent := par;
end;
registerAction(eaInsertB,new);
end;
procedure addChildElement(el,new : dword);
//add element[new] as child element to el
begin
element[el].child := new;
element[new].parent := el;
registerAction(eaAddChild,new);
end;
procedure replaceElement(el,new : dword);
//replace element el by new
var nxt,prv,par,chd : dword;
begin
with element[el] do
begin
par := parent;
nxt := next;
prv := previous;
chd := child;
end;
with element[new] do
begin
parent := par;
previous := prv;
next := nxt;
child := chd;
end;
if prv > 0 then element[prv].next := new
else element[par].child := new;
if nxt > 0 then element[nxt].previous := new;
registerAction(eaReplace,el);
end;
procedure packElement(el : dword);
//make el child of previous
var chd,nxt,prv : dword;
begin
prv := element[el].previous;
if prv = 0 then exit; //must have previous
nxt := element[el].next;
chd := element[prv].child;
if chd > 1 then //if children, skip
begin
repeat until nextEl(chd) = false;
element[chd].next := el;
end
else //if previous has no children
begin
element[prv].child := el;
element[el].previous := 0;
end;
element[el].next := 0;
element[el].previous := chd;
element[el].parent := prv;
element[prv].next := nxt;
element[nxt].previous := prv;
registerAction(eaPack,el);
end;
procedure unpackelement(el : dword);
//place last child as next of parent
var par,nxt,prv : dword;
begin
if element[el].next <> 0 then exit; //el must be last child
with element[el] do
begin
par := parent;
nxt := element[par].next;
prv := previous;
end;
if prv = 0 then element[par].child := 0
else element[prv].next := 0;
element[par].next := el;
element[el].previous := par;
if nxt > 0 then
begin
element[el].next := nxt;
element[nxt].previous := el;
end;
par := element[par].parent;
element[el].parent := par;
end;
procedure restoreElement(el : dword);
//un-delete
var prv,nxt,par : dword;
begin
with element[el] do
begin
prv := previous;
nxt := next;
par := parent;
end;
if (prv = 0) and (par = 0) then exit; //already destroyed or element 1
if prv > 0 then element[prv].next := el
else element[par].child := el;
if nxt > 0 then element[nxt].previous := el;
end;
procedure resetElement(el : dword);
//un-replace
//destroy replaced element
var des,nxt,prv,par : dword;
begin
with element[el] do
begin
prv := previous;
nxt := next;
par := parent;
if prv = 0 then des := element[par].child
else des := element[prv].next;
end;
if prv = 0 then element[par].child := el
else element[prv].next := el;
if nxt > 0 then element[nxt].previous := el;
clearElement(des);
inc(freeElementCount);
end;
procedure destroyElement(el : dword);
//destroy element and it's children
//adjust freeElementcount
var mcode : TTreeMove;
oldEl,prv,nxt,par : dword;
begin
with element[el] do
begin
if getElementStatus(el) = esActive then //delete first if active
begin
nxt := next;
prv := previous;
par := parent;
if prv > 0 then element[prv].next := nxt
else if par > 0 then element[par].child := nxt;
if nxt > 0 then element[nxt].previous := prv;
end;//if
parent := 0; //isolate element, keep link to children
next := 0;
previous := 0;
end;
mcode := tmChild;
repeat
OldEl := el;
mcode := ScanElement(el,mcode);
if (mcode = tmPrevious) or (mcode = tmParent) or (mcode = tmNone) then
begin
clearElement(oldEl);
inc(freeElementcount);
end;
until mcode = tmNone;
end;
//--- undo ---
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 purgeUndoStack;
//remove bottom of undo stack to make space
var link : boolean;
i : word;
begin
if lastAction = 0 then exit;
repeat
with undolist[1] do
case action of
eaInsertA,
eaInsertB : ; //no action
eaReplace,
eaDelete : destroyElement(elmnt);
end;//case
if lastaction > 1 then link := undolist[2].bwlink
else link := false;
for i := 1 to lastAction - 1 do undolist[i] := undolist[i+1];
with undolist[lastaction] do
begin
elmnt := 0;
action := eaNone;
bwlink := false;
end;
dec(lastaction);
until link = false;
end;
procedure registerAction(ea : TElmntAction; el : dword);
begin
if lastAction = maxAction then purgeUndoStack;
inc(lastAction);
with undolist[lastaction] do
begin
elmnt := el;
action := ea;
bwlink := linkflag;
end;
linkflag := startflag;
end;
procedure UndoElement;
//reverse last operation
var link : boolean;
begin
if lastAction = 0 then exit;
repeat
with undolist[lastaction] do
begin
case action of
eaInsertA,
eaInsertB,
eaAddChild : destroyElement(elmnt);
eaReplace : resetElement(elmnt);
eaDelete : restoreElement(elmnt);
eaPack : unpackElement(elmnt);
end;//case
link := bwlink;
end;//with
dec(lastaction);
until link = false;
end;
end.
|
|