Unit U_Elevator1;
{Copyright  © 2003, Gary Darby,  www.DelphiForFun.org
 This program may be used or modified for any non-commercial purpose
 so long as this original notice remains in place.
 All other rights are reserved
 }

{This is version 1 of an elevator simulator.  This version implements manual
  control of up to 4 elevators traveling across 2 to 8 floors with up/down Call
  buttons outside of the elevators and Floor button inside each car.

  A simple control strategy is implemented.  Elevators have enough internal
  intelligence to handle all floor and call buttons moving in one direction then
  those requests requiring travel in the opposite direction.  Calls are assigned
  by the scheduler to the first stopped car found.}


{ TODO : Animate customer arrivals/departures }

{ TODO : Elevator control parameters
             Minfloor, Maxfloor (or list of eligible floors)
             Home floor
             Idle delay before returning home
             Default door open time
             Elevator speed
             }

{ TODO : Generated customer script
          (arrival/destination distributions by floor and time of day)}

{ DONE :  Recognize floorbtn pushes as entered   }

{ DONE : If there is a stopped elevator at a floor when a
         call button is pushed, let that elevator handle it}

{ TODO : User slected call handling strategies (prioritize tests?):
            1. Lowest # stopped elevator. (current startegy)
            2. Closest stopped elevator.
            3  Closest that will stop here anyway and is moving in the right directon.
            4. Closest that will pass this floor moving in the right direction.
            5. Close moving in the right direction.
            6. Closest stopped that has been moving in the right direction.
            7. ???
          }

{ DONE : Bug - Handle calls to a floor with a stopped elevator  by that
          elevator}

{ DONE : Popup elevator panel beside the elevator }

{ TODO : Check for memory leaks }

{ TODO :  Add statistics:
              Case name
              total run time
              # passenegers
              mean, max, min wait times
              mean. max, min elevator utilization
              ???
           }


interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Spin, CheckLst, shellAPI;

type
Tdirection=(up, down, none);

  TCallBtn=class;  {forward declaration}

TCallrec=record {used in Stops, array of scheduled calls waiting for each elevator}
direction:TDirection; {waiting to go up or down?}
callbtn:TCallbtn; {pointer to actual button so we can turn it off when we get there}
end;

{TCallBtn}
TCallBtn=class(TImage)
public
pushed:boolean;
    floornbr:integer;
    direction:TDirection;
    darkImage, LitImage:TBitmap;
constructor create(Aowner:TComponent; newright,base, fnbr,fheight:integer;
                  newdirection:TDirection;
                  b1,b2:TBitmap);  reintroduce;
{"reintroduce" just tells Delphi that we know that we have
                   redefined the parameter list for an existing method, so there
                   is no need to give a warning message to that effect}
destructor destroy; override;
{Similarly "override" tells Delphi that we know that we have defined a
     method with the same name and parameter list an existing method, so
     there is no need to give a warning message to that effect}

procedure turnon;
procedure turnoff;
end;

{TElevator TThread descendent so ekevators run independently}
TElevator=class(TThread)
    image1,image2:TShape; {2 door halves}
moving:boolean;  {true if moving}
Direction:TDirection; {direction to move, up or down}
number:integer; {Elevator number}

{Dimensions}
doorwidth:integer;
    doorheight:integer;
    leftside:integer;
    w:integer;   {door width while opening or closng doors}

{Movement}
incr:integer; {move step size in pixels}
newtop:integer;
    dooropen:boolean;
    doorclosing:boolean;
    timedooropened:TDatetime;

    stops:array of TCallrec; {Array of pending calls with one entry for each floor }
floorbtns:array of boolean; {Array of car buttons, one for each destination floor}
floornumber:integer;
    minfloor, maxfloor:integer;
    floorbase:integer;
    floorheight:integer;

constructor create(Aowner:TControl;
                       newnumber, newleftside, newwidth, newheight,
                       newminfloor, newmaxfloor,newfloorbase,
                       newfloorheight:integer;
                       mouseup:TMouseEvent);
destructor destroy; override;
procedure execute; override;
procedure moveit;
procedure opendoor;
procedure opendoorstep;
procedure closedoor;
procedure closedoorstep;
function DoorOpenTime:integer;
function stopcount(dir:Tdirection; startfloor, stopfloor:integer):integer;
function idle:boolean;
end;


{TFloor }
TFloor=class(TObject)
    Upbutton:TCallBtn;
    DownButton:TCallBtn;
    floornbr:integer;
constructor create(Aowner:TComponent; R:Trect; n,Fheight:integer;
                b1,b2,b3,b4:TBitmap);
destructor  destroy;   override;
end;

{TForm}
TForm1 = class(TForm)
    FloorEdit: TSpinEdit;
    Label1: TLabel;
    Image1: TImage;
    ElevEdit: TSpinEdit;
    Label2: TLabel;
    Timer1: TTimer;
    ElPanel: TPanel;
    ElLbl: TLabel;
    Label4: TLabel;
    ElButtonBox: TCheckListBox;
    CloseBtn: TButton;
    StaticText1: TStaticText;
    Memo1: TMemo;
    Label3: TLabel;
procedure FormActivate(Sender: TObject);
procedure FloorEditChange(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure ElevEditChange(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure CloseBtnClick(Sender: TObject);
procedure ElMouseUp(Sender: TObject; Button: TMouseButton;
                        Shift: TShiftState; X, Y: Integer);
procedure StaticText1Click(Sender: TObject);
procedure ElButtonBoxClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
el:array of TElevator;
    floorheight:integer;
    b1,b2,b3,b4:TBitmap;    {up/down, light/dark call button images{}
floor:array of TFloor;
    nbrfloors:integer;
    nbrelevators:integer;
    pendingIn:array of TCallrec; {pending call buttons}
showingpanel:integer;
procedure setupfloors;
procedure CallBtnClick(Sender: TObject);
procedure setupElevators(nfloors:integer);
procedure scheduler;
function  onElevator(sender:TObject; x,y:Integer):integer;
end;

var
Form1: TForm1;

implementation
{$R *.dfm}

var
defaultDooropentime:integer=5;
  DefaultFloorMoveIncrement:integer=2;
  DefaultDoorMoveIncrement:Integer=2;

{In case Math uinit is not available}
function min(a,b:integer):integer;
begin
if a<=b then result:=a else result:=b;
end;
function max(a,b:integer):integer;
begin
if a>=b then result:=a else result:=b;
end;

{******************* TElevator.create ********}
constructor TElevator.create  (Aowner:TControl;
                       newnumber, newleftside, newwidth, newheight,
                       newminfloor, newmaxfloor,newfloorbase,
                       newfloorheight:integer; Mouseup:TMouseEvent);
var j:integer;
begin
inherited create(true);
    number:=newnumber;
    incr:=2;
    moving:=false;
    doorwidth:=newwidth div 2;
    w:=doorwidth;
    direction:=up;
    freeonterminate:=false;
    minfloor:=newminfloor-1;
    maxfloor:=newmaxfloor-1;
    floorheight:=newfloorheight;
    floorbase:=newfloorbase-newheight;
    leftside:=newleftside;
    doorwidth:=newwidth div 2;
    doorheight:=newheight;
    dooropen:=false;
    doorclosing:=false;
    suspended:=false;
    image1:=TShape.create(Aowner);
with image1 do
begin
shape:=strectangle;
      left:=leftside;
      top:=floorbase;
      width:=doorwidth;
      height:=newheight;
      parent:=twincontrol(aowner);
      brush.color:=$4080ff;
end;

    image2:=TShape.create(Aowner);
with image2 do
begin
shape:=strectangle;
      left:=leftside+doorwidth;
      top:=floorbase;
      width:=doorwidth;
      height:=newheight;
      parent:=twincontrol(aowner);
      brush.color:=$4080ff;
end;
    setlength(stops,maxfloor+1);
    setlength(floorbtns, maxfloor+1);

for j:=0 to maxfloor do
begin
stops[j].direction:=none;
      floorbtns[j]:=false;
end;

    image1.OnMouseup:=mouseUp;
    image2.OnMouseUp:=MouseUp;
end;

{*********** TElevator.Destroy **********}
destructor TElevator.destroy;
begin
freeandnil(image1);
    freeandnil(image2);
inherited;
end;

{***************** TElevator.execute ***********}
procedure TElevator.execute;
{The elevator processing loop}
var
i:integer;
begin
while not terminated do  {we only get to execute one time, so
           must keep running until we're all done}
begin
newtop:=floorbase - floornumber*floorheight; {put back on exact boundary}
while moving do
begin
if dooropen then closedoor;
case direction of
up: {if moving up}
begin
dec(newtop,incr);
{are we at the next higher floor?}
if newtop<=floorbase - (floornumber+1)*floorheight then
begin {yes - stop}
floornumber:=min(floornumber+1,maxfloor);
if floornumber=maxfloor then direction:=down;
              newtop:=floorbase - floornumber*floorheight; {put back on exact boundary}
if (stops[floornumber].direction=direction)
or ((stops[floornumber].direction=down) and (stopcount(up, floornumber+1,maxfloor)=0))
or (floorbtns[floornumber])
then
begin
synchronize(opendoor);
                moving:=false;
if terminated then exit;
if stops[floornumber].direction in [up,down] then
begin
stops[floornumber].callbtn.turnoff;
                  stops[floornumber].direction:=none;
end;
                floorbtns[floornumber]:=false;
end;
end;
end;
        down:
begin
inc(newtop,incr);
{are we at the next lower floor?}
if newtop>=floorbase - ((floornumber-1)*floorheight)  then
begin {yes - stop}
floornumber:=max(floornumber-1,minfloor);
if floornumber=minfloor then direction:=up;
              newtop:=floorbase - floornumber*floorheight; {put back on exact boundary}
if (stops[floornumber].direction=direction)
or ((stops[floornumber].direction=up) and (stopcount(down, 0,floornumber-1)=0))
or (floorbtns[floornumber])
then
begin
synchronize(opendoor);
                moving:=false;
if terminated then exit;
if stops[floornumber].direction in  [up,down] then
begin
stops[floornumber].callbtn.turnoff;
                  stops[floornumber].direction:=none;
end;
                floorbtns[floornumber]:=false;
end
end;
end;
end; {case}
if terminated then exit;
if moving then synchronize(moveit); {move one step}
sleep(20);
end; {of moving loop}

{not moving anymore}

{Check Stops and Floorbtn array for next target}
if terminated then exit;

      floorbtns[floornumber]:=false;
If stops[floornumber].direction in  [up,down]
then  {If the call was for the current floor, no action necessary, except
             turning off the call button}
begin
stops[floornumber].callbtn.turnoff;
        stops[floornumber].direction:=none;
end;

if {(length(stops)>0) and} (not dooropen) and (not doorclosing) then
begin
{check rest of array in our last direction}
if direction=up then
begin
for i:=floornumber+1 to high(stops) do
if (stops[i].direction=up)
or ((i=high(stops)) and (stops[i].direction=down))
or floorbtns[i] then
begin
moving:=true;
            break;
end;
end
else
if direction=down then
for i:=floornumber-1 downto 0 do
if (stops[i].direction=down)
or ((i=0) and (stops[i].direction=up))
or floorbtns[i] then
begin
moving:=true;
          break;
end;

{check rest of arrays in direction opposite to
         our last direction}
if direction=down then
begin
for i:=floornumber+1 to high(stops) do
if (stops[i].direction=up) or floorbtns[i] then
begin
moving:=true;
            direction:=up;
            break;
end;
end
else
if direction=up then
for i:=floornumber-1 downto 0 do
if (stops[i].direction=up) or floorbtns[i] then
begin
moving:=true;
          direction:=down;
          break;
end;

if not moving then
begin  {see if we can change directions a satisfy a call}
for i:=high(stops) downto 0 do
if stops[i].direction in [up,down] then
begin
if floornumber<i then direction:=up
else if floornumber>i then direction:=down;
            moving:=true;
            break;
end;
end;
end;
      sleep(100);
end;
end;

{********* TElevator.moveit *********}
procedure TElevator.moveit;
{the synchronized update of teh elevator image}
begin
image1.top:=newtop;
  image2.top:=newtop;
end;

{*************** TElevator.OpenDoorStep *****}
procedure TElevator.opendoorstep;
{Synchronized update of doors opening image}
begin
image1.width:=w;
  image2.width:=w;
  image2.left:=image2.left+2;
  image1.Update; image2.Update;
with TCustomForm(image1.Parent).canvas do
begin
pen.Width:=2;
    moveto(image1.Left+1,image1.top+1);
    lineto(image2.Left+1,image1.Top+1);
    moveto(image1.Left+1,image1.top+image1.height-1);
    lineto(image2.Left+1,image1.Top+image1.height-1);
end;
  sleep(20);
end;

{***************** TElevator.CloseDoorStep ***********}
procedure TElevator.closedoorstep;
{Synchronized update of doors closing image}
begin
image1.width:=w;
  image2.width:=w;
  image2.left:=image2.left-2;
  image1.Update; image2.Update;
  sleep(20);
end;

{************* TElevator.OpenDoor *****}
procedure Televator.opendoor;
var i:integer;
    n:integer;
begin
w:=doorwidth;
while w>4 do
begin
dec(w,2);
if terminated then exit;
      opendoorstep;
end;
    dooropen:=true;
    timedooropened:=now;
{build the mini elevator panel image}
with TcustomForm(image1.parent).canvas do
begin
brush.color:=claqua;    {the panel}
rectangle(image1.left+doorwidth-5, image1.top+5,
                image1.left+doorwidth+5, image1.top+image1.height-5);
      n:=high(floorbtns);
for i:=0 to n do
begin  {the buttons}
if floorbtns[i] and (i<>floornumber)
then brush.color:=clyellow else brush.color:=clgray;
        pen.color:=brush.color;
        ellipse(image1.left+doorwidth-2, image1.top+7 + (n-i)*6,
                image1.left+doorwidth+2, image1.top+11 + (n-i)*6);
end;
end;
end;

{********** TElevator.closeDoor *********}
procedure Televator.closedoor;
begin
doorclosing:=true;
while w<doorwidth do
begin
inc(w,2);
if terminated then exit;
      synchronize(closedoorstep);
end;
    dooropen:=false;
    doorclosing:=false;
end;

{************* TElevator.DoorOpenTime ********}
function TElevator.DoorOpenTime:integer;
{Return the number of seconds doors have been open,
 return 0 if doors are closed}
begin
if dooropen
then result:=trunc((now-timedooropened)*secsperday)
else result:=0;
end;

{***************** TElevator.StopCount ***************}
function TElevator.stopcount(dir:Tdirection; startfloor, stopfloor:integer):integer;
{count the number of stops in direction "dir" between startrfloor and stopfloor}
var
i:integer;
begin
result:=0;
for i:=startfloor to stopfloor do
if stops[i].direction=dir then inc(result);
end;

{*********** Televator.idle **************}
function TElevator.idle:boolean;
{elevator is idle if it is stopped and there are no pending actions}
var i:integer;
begin
result:=true;
for i:=minfloor to maxfloor do
begin
if (stops[i].direction in [up,down]) or floorbtns[i] then
begin
result:=false;
       break;
end;
end;
end;


{************* TCallBtn.Create ***********}
constructor TCallBtn.create(Aowner:TComponent;
                         newright,base,fnbr,fheight:integer;
                         newdirection:TDirection; b1,b2:TBitmap);
begin
inherited create(aowner);
  darkimage:=TBitmap.create;
  darkimage.assign(b1);
  litimage:=TBitmap.create;
  litimage.assign(b2);
  floornbr:=fnbr;
  picture.bitmap:=darkimage;
  pushed:=false;
  direction:=newdirection;
if b1<>nil then
begin
top:=base - fnbr*Fheight-b1.height;
    height:=b1.height;
end
else
begin
top:=0;
    height:=0;
end;
  left:=newright;
  parent:=TWinControl(aowner);
end;

{************* TCallBtn.TurnOn ********}
procedure TCallBtn.turnon;
begin
pushed:=true;
   picture.bitmap:=litimage;
end;

{*********** TCallBtn.TurnOff *********}
procedure TCallBtn.turnoff;
begin
pushed:=false;
   picture.bitmap:=darkimage;
end;


{*********** TCallBtn.Destroy ***********}
destructor TCallBtn.destroy;
begin
darkimage.free;
  litimage.free;
inherited;
end;


{**************** TFloor.Create ***************}
constructor TFloor.create(Aowner:TComponent; R:TRect; n,Fheight:integer;
                b1,b2,b3,b4:TBitmap);
begin
inherited create;
  floornbr:=n;

  upbutton:=TCallBtn.create (Aowner,r.right,r.bottom,n,fheight,up, b1, b2);
  downbutton:=TCallBtn.create(Aowner,r.right,r.bottom,n,fheight,down, b3, b4);

if b1<>nil then upbutton.top:=upbutton.top-b1.height-5;
  upbutton.turnoff;
  downbutton.turnoff;
end;

{*********** TFloor.Destroy **********}
destructor TFloor.destroy;
begin
upbutton.destroy;
  downbutton.destroy;
inherited;
end;

{********* TForm1.SetupElevators *******}
procedure TForm1.setupelevators(nfloors:integer);
var i:integer;
      elevatorwidth, elevatorheight:integer;
begin
if length(el)>0 then
for i:=0 to high(el) do
if assigned(el[i]) then
with eL[i] do
begin
terminate;
      waitfor;
      free;
end;

    nbrelevators:=elevEdit.value;
    setlength(el,nbrelevators);
    elevatorwidth:=image1.width div nbrelevators;
    elevatorheight:=7*floorheight div 8;
    elevatorwidth:=min(elevatorheight div 2, elevatorwidth);
for i:=0 to nbrelevators-1 do
begin
el[i]:=Televator.create(self,
              i {elevator #},
              image1.left+10+(elevatorwidth+15)*i {left},
              elevatorwidth {width}, elevatorheight {height},
              1 {minfloor}, nfloors  {max floor},
              image1.top+image1.height - 2 {floorbase (of 1st floor)},
              floorheight {floor height}, ElMouseup);
end;
end;

{********** TForm1.FormActivate *******}
procedure TForm1.FormActivate(Sender: TObject);

begin
doublebuffered:=true;
   b1:=TBitmap.create;
   b1.loadfromfile('ArrowUpDark2.bmp');
   b1.transparent:=true;
   b1.transparentmode:=tmAuto;

   b2:=TBitmap.create;
   b2.loadfromfile('ArrowUpLit2.bmp');

   b3:=TBitmap.create;
   b3.loadfromfile('ArrowDownDark2.bmp');

   b4:=TBitmap.create;
   b4.loadfromfile('ArrowDownLit2.bmp');

   FloorEditchange(sender); {set up initial floors and elevators}
ElPanel.color:=$56C4E3;
   timer1.enabled:=true;
end;


{************** FloorEditChange ***********}
procedure TForm1.FloorEditChange(Sender: TObject);
{User changed the number of floors}
begin
timer1.enabled:=false;
   Setupfloors;
   timer1.enabled:=true;
end;

{*********** SetupFloors ********}
procedure TForm1.SetupFloors;
var i:integer;
    R:TRect;

begin
nbrfloors:=Flooredit.value;
  floorheight:=image1.height  div nbrfloors;
  setupelevators(nbrfloors);
for i:= 0 to high(floor) do floor[i].destroy;
  setlength(floor,nbrfloors);
  setlength(PendingIn,nbrfloors);
  elbuttonbox.items.clear;

with image1 do  R:=rect(left,top,left+width,top+height);
for i:= nbrfloors-1 downto 0 do
begin
elbuttonbox.items.add(inttostr(i+1));
    PendingIn[i].direction:=none;
If (i>0) and (i<nbrfloors-1) then
floor[i]:=TFloor.create(self,R,i,floorheight,b1,b2,b3,b4)
else If i=nbrfloors-1 then
floor[i]:=TFloor.create(self,R,i,floorheight,nil,nil,b3,b4)
else If i=0 then
floor[i]:=TFloor.create(self,R,i,floorheight,b1,b2,nil,nil);

    floor[i].upbutton.onclick:=CallBtnClick;
    floor[i].downbutton.onclick:=CallBtnClick;

end;
  invalidate;
end;

{************** CallBtnClick *************}
procedure TForm1.CallBtnClick(Sender: TObject);
{Call button click exit}
begin
with TCallBtn(sender) do
begin
turnon;
    PendingIn[floornbr].direction:=direction;
    pendingIn[floornbr].callbtn:=TCallBtn(sender);
end;
end;

{*************** FormPaint **************}
procedure TForm1.FormPaint(Sender: TObject);
{Draw the floors}
var
i:integer;
begin
with canvas do
begin
pen.width:=3;
    pen.color:=clblack;
for i:=0 to nbrfloors-1 do
begin
moveto( 0, image1.top+image1.height-i*floorheight-2);
      lineto(image1.width,image1.top+image1.height-i*floorheight-2);
end;
end;
end;

{************* ElevEditChange ************}
procedure TForm1.ElevEditChange(Sender: TObject);
begin
timer1.enabled:=false;
  setupelevators(nbrfloors);
  timer1.enabled:=true;
end;

{*************** Timer1Timer *************}
procedure TForm1.Timer1Timer(Sender: TObject);
var  i:integer;
{Close open elevator doors after default number of seconds}
{Also call the scheduler routine to control elevator movement}
begin
for i:=0 to high(el) do
with el[i] do
if dooropentime>defaultdooropentime then
begin
closedoor;
if i=showingpanel then elpanel.visible:=false;
end;
  scheduler;
end;

{************* Scheduler **********}
procedure TForm1.Scheduler;
var
i,j:integer;
begin
for i:=0 to nbrfloors-1 do
begin
{first, check if there is an idel elevator at this floor}
if  pendingIn[i].direction in [up,down]then
begin
for j:=0 to nbrelevators-1 do
if el[j].idle and (el[j].floornumber=i)  then
begin
el[j].stops[i]:=pendingIn[i];
        pendingIn[i].direction:=none;
        break;
end;
end;

{If no idle elevator here, search for one to handle the call}
if  pendingIn[i].direction in [up,down]then
begin
for j:=0 to nbrelevators-1 do
with el[j] do
begin

if (not moving) and (not dooropen) then
begin
stops[i]:=pendingIn[i];
          pendingIn[i].direction:=none;
          break;
end;
end;
end;
end;
end;

{********************** ElMouseUp ******************}
procedure TForm1.ElMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
{User clicked on floor/elevator image - check which elevator, if any,
 and display the elevator control panel}
var i,n:integer;
begin
n:=onElevator(sender,x,y);
if (n>=0) then
begin
with el[n] do
if not moving then
begin
for i:=0 to high(floor) do  elButtonBox.checked[i]:=floorbtns[nbrfloors-1-i];
if not dooropen then synchronize(opendoor);
end;

    showingpanel:=n;
    elLbl.caption:='Elevator # '+inttostr(n+1);
with elpanel do
begin
top:=el[n].image1.top-height div 2;
if top<0 then top:=5;
      left:=el[n].leftside+2*el[n].doorwidth + 5;
      visible:=true;
end;
end;
end;

{********  OnElevator **********}
function  Tform1.onElevator(sender:TObject; x,y:Integer):integer;
{If point x,y is on an elevator image, return its number}
{Used when evaluating user clicks}
var i:integer;
begin
result:=-1;
for i:=0 to high(el) do
with el[i] do
begin
if (sender=image1) or (sender=image2)
or (
        (y>image1.top) and (y<Image1.top+image1.height) and
(x>image1.left) and (x<(image2.left+image2.width))
       )
then
begin
result:=i;
      break;
end;
end;
end;

{*************** CloseBtnClick ***********}
procedure TForm1.CloseBtnClick(Sender: TObject);
{A set of elevator buttons has been selected}
var i:integer;
begin
elPanel.visible:= false;
  el[showingpanel].closedoor;
end;

{************** StaticText1Click ************}
procedure TForm1.StaticText1Click(Sender: TObject);
{ Browse to DFF homepage }
begin
ShellExecute(Handle, 'open', 'http://www.delphiforfun.org/',
nil, nil, SW_SHOWNORMAL);
end;

{************ ElButtonBocClick ************}
procedure TForm1.ElButtonBoxClick(Sender: TObject);
{An elevator floor button was pushed}
var
i:integer;
begin
with el[showingpanel] do
for i:= 0 to high(floor) do
elbuttonbox.checked[nbrfloors-1-i]:=floorbtns[i];

with elbuttonbox do
begin
checked[itemindex]:= not checked[itemindex];
    el[showingpanel].floorbtns[nbrfloors-1-itemindex]:=checked[itemindex];
    invalidate;
end;

end;

end.