unit Zoom;
{ --------------------------------------------------------------------------
  WinWCP - ZOOM MODULE V1.0  (c) J. Dempster 1997
  Modal form which allows user to zoom in/out of display of recorded signals
  --------------------------------------------------------------------------}
interface

uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
  StdCtrls, ExtCtrls, Global, Shared, SysUtils, FileIO, maths ;

type
  TZoomFrm = class(TForm)
    pbDisplay: TPaintBox;
    cbChannels: TComboBox;
    Label1: TLabel;
    lbTMin: TLabel;
    lbTMax: TLabel;
    ChannelsGrp: TGroupBox;
    ckInUse0: TCheckBox;
    ckInUse1: TCheckBox;
    ckInUse2: TCheckBox;
    ckInUse3: TCheckBox;
    ckInUse4: TCheckBox;
    ckInUse5: TCheckBox;
    bOK: TButton;
    bCancel: TButton;
    procedure FormActivate(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure pbDisplayMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pbDisplayMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure cbChannelsChange(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure bOKClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
    ChOnDisplay : LongInt ;
    procedure ZoomOut ;
  end;

var
  ZoomFrm: TZoomFrm;

implementation

{$R *.DFM}

uses MDIform ;

type
TMousePos = ( TopLeft,
              TopRight,
              BottomLeft,
              BottomRight,
              MLeft,
              MRight,
              MTop,
              MBottom,
              MDrag ) ;

var
   ZoomCh : TChannel ;
   ZoomBox : TRect ;
   MousePos : TMousePos ;
   MoveZoomBox : Boolean ;
   XOld,YOld : Integer ;
   RecHeader : ^TRecHeader ;
   ADC : ^TIntArray ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }


procedure TZoomFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                New(ADC) ;
                New(RecHeader) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(ADC) ;
                Dispose(RecHeader) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TZoomFrm.FormActivate(Sender: TObject);
{ -------------------------------
  Initialisation when form opens
  ------------------------------}
var
   ch : Longint ;
begin
     { Create buffers }
     HeapBuffers( Allocate ) ;

     { Activate channel in use check boxes }

     if FH.NumChannels > 0 then begin
          ckInUse0.caption := 'Ch.0 ' + Channel[0].ADCName  ;
          ckInUse0.enabled := True ;
          ckInUse0.visible := True ;
          ckInUse0.checked := Channel[0].InUse ;
          end
     else begin
          ckInUse0.enabled := False ;
          ckInUse0.visible := False ;
          ckInUse0.checked := False ;
          end ;

     if FH.NumChannels > 1 then begin
          ckInUse1.caption := 'Ch.1 ' + Channel[1].ADCName  ;
          ckInUse1.enabled := True ;
          ckInUse1.visible := True ;
          ckInUse1.checked := Channel[1].InUse ;
          end
     else begin
          ckInUse1.enabled := False ;
          ckInUse1.visible := False ;
          ckInUse1.checked := False ;
          end ;

     if FH.NumChannels > 2 then begin
          ckInUse2.caption := 'Ch.2 ' + Channel[2].ADCName  ;
          ckInUse2.enabled := True ;
          ckInUse2.visible := True ;
          ckInUse2.checked := Channel[2].InUse ;
          end
     else begin
          ckInUse2.enabled := False ;
          ckInUse2.visible := False ;
          ckInUse2.checked := False ;
          end ;

    if FH.NumChannels > 3 then begin
          ckInUse3.caption := 'Ch.3 ' + Channel[3].ADCName  ;
          ckInUse3.enabled := True ;
          ckInUse3.visible := True ;
          ckInUse3.checked := Channel[3].InUse ;
          end
     else begin
          ckInUse3.enabled := False ;
          ckInUse3.visible := False ;
          ckInUse3.checked := False ;
          end ;


     if FH.NumChannels > 4 then begin
          ckInUse4.caption := 'Ch.4 ' + Channel[4].ADCName  ;
          ckInUse4.enabled := True ;
          ckInUse4.visible := True ;
          ckInUse4.checked := Channel[4].InUse ;
          end
     else begin
          ckInUse4.enabled := False ;
          ckInUse4.visible := False ;
          ckInUse4.checked := False ;
          end ;

     if FH.NumChannels > 5 then begin
          ckInUse5.caption := 'Ch.5 ' + Channel[5].ADCName  ;
          ckInUse5.enabled := True ;
          ckInUse5.visible := True ;
          ckInUse5.checked := Channel[5].InUse ;
          end
     else begin
          ckInUse5.enabled := False ;
          ckInUse5.visible := False ;
          ckInUse5.checked := False ;
          end ;


     { Fill channel selection list }
     cbChannels.Clear ;
     for ch := 0 to FH.NumChannels-1 do
          cbChannels.items.add( ' ' + Channel[ch].ADCName ) ;

    { Start with channel 0 selected }
    chOnDisplay := IntLimitTo( ChOnDisplay, 0, fH.NumChannels-1 ) ;
    cbChannels.ItemIndex := ChOnDisplay ;

    { Set scaling for the channel to be worked on }

    ZoomCh := Channel[ChOnDisplay] ;
    ZoomCh.Left := pbDisplay.Width div 50 ;
    ZoomCh.Right := pbDisplay.Width - ZoomCh.Left;
    ZoomCh.Top := pbDisplay.Height div 50 ;
    ZoomCh.Bottom := pbDisplay.Height - ZoomCh.Top ;

    ZoomCh.xMin := 0. ;
    ZoomCh.xMax := FH.NumSamples  ;
    ZoomCh.yMin := MinADCValue  ;
    ZoomCh.yMax := MaxADCValue  ;

    pbDisplay.Refresh ;
    MoveZoomBox := False ;

    end;

procedure TZoomFrm.FormPaint(Sender: TObject);
var
   i,ChOffset,ch : LongInt ;
   x,y,dx : single ;
   xPix,yPix : Integer ;

begin
     { Load record from file }
     GetRecord( fH, RecHeader^, FH.CurrentRecord, ADC^ ) ;

     Ch := cbChannels.ItemIndex ;
     ChOnDisplay := Ch ;

     { Erase Display }
     pbDisplay.canvas.brush.color := clWhite ;
     pbDisplay.canvas.fillrect(pbDisplay.canvas.ClipRect);

     { Set trace colour }

     pbDisplay.canvas.pen.color := ZoomCh.color ;

     { Set scaling }
     ZoomCh.xScale := (ZoomCh.Right - ZoomCh.Left) / (ZoomCh.xMax - ZoomCh.xMin ) ;
     ZoomCh.yScale := (ZoomCh.Bottom - ZoomCh.Top) / (ZoomCh.yMax - ZoomCh.yMin ) ;

     { Set size of zoom box }

     ZoomBox.Left :=  Trunc((Channel[ch].xMin - ZoomCh.xMin)*ZoomCh.xScale)
                      + ZoomCh.Left ;
     ZoomBox.Right := Trunc((Channel[ch].xMax - ZoomCh.xMin)*ZoomCh.xScale)
                       + ZoomCh.Left ;
     ZoomBox.Top := ZoomCh.Bottom -
                     Trunc((Channel[ch].yMax - ZoomCh.yMin)*ZoomCh.yScale) ;
     ZoomBox.Bottom := ZoomCh.Bottom -
                     Trunc((Channel[ch].yMin - ZoomCh.yMin)*ZoomCh.yScale) ;

     { Display labels }

     lbTMin.caption := Format( '%5.4g %s', [0.,Settings.TUnits] ) ;
     lbTMax.caption := Format( '%5.4g %s',
                               [FH.NumSamples*RecHeader^.dt*Settings.TScale,
                                Settings.TUnits] ) ;
     lbTMin.left := pbDisplay.left ;
     lbTMin.Top := pbDisplay.Top + pbDisplay.Height + 1 ;
     lbTMax.Left := pbDisplay.Left + pbDisplay.width - lbTMax.Width ;
     lbTMax.Top := lbTMin.Top ;

     { Plot channel}

     dx := 1. ;
     x := 0. ;
     ChOffset := Channel[ch].ChannelOffset ;
     for i := 0 to FH.NumSamples-1 do begin

             { Note that channels are stored in the A/D buffer
               in reverse order. i.e. with the highest channel no.
               first (e.g. C3,C2,C1,C0,C3,C2,C1,.... }
             y := ADC^[(i*FH.NumChannels) + ChOffset ] ;
             xPix := Trunc(ZoomCh.xScale*(x - ZoomCh.xMin) + ZoomCh.Left) ;
             yPix := Trunc(ZoomCh.Bottom - ZoomCh.yScale*(y - ZoomCh.yMin));
             if i = 0 then pbDisplay.canvas.moveto(xPix,yPix)
                      else pbDisplay.canvas.lineto(xPix,yPix);
             x := x + dx ;
             end ;

     pbDisplay.canvas.DrawFocusRect( ZoomBox ) ;

     MoveZoomBox := False ;
     end ;


procedure TZoomFrm.pbDisplayMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     MoveZoomBox := True ;
     end;

procedure TZoomFrm.pbDisplayMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
const
     Margin = 10 ;
     ZoomMin = 10 ;
var
   Height,Width : LongInt ;
begin

     { Find MousePos of mouse relative to Zoom box
       and change mouse cursor shape appropriately }

     if not MoveZoomBox then begin

        if (Abs(X - ZoomBox.Left) + Abs(Y - ZoomBox.Top)) < Margin then begin
            pbDisplay.Cursor := crSizeNWSE ;
            MousePos := TopLeft ;
            end
        else if (Abs(X - ZoomBox.Right) + Abs(Y - ZoomBox.Top)) < Margin then begin
            pbDisplay.Cursor := crSizeNESW ;
            MousePos := TopRight ;
            end
        else if (Abs(X - ZoomBox.Left) + Abs(Y - ZoomBox.Bottom)) < Margin then begin
            pbDisplay.Cursor := crSizeNESW ;
            MousePos := BottomLeft ;
            end
        else if (Abs(X - ZoomBox.Right) + Abs(Y - ZoomBox.Bottom)) < Margin then begin
            pbDisplay.Cursor := crSizeNWSE ;
            MousePos := BottomRight ;
            end
        else if (Abs(X - ZoomBox.Left) < Margin ) and
             (Y <= ZoomBox.Bottom) and (Y >= ZoomBox.Top ) then begin
            pbDisplay.Cursor := crSizeWE ;
            MousePos := MLeft ;
            end
        else if (Abs(X - ZoomBox.Right) < Margin) and
               (Y <= ZoomBox.Bottom) and (Y >= ZoomBox.Top ) then begin
            pbDisplay.Cursor := crSizeWE ;
            MousePos := MRight ;
            end
        else if (Abs(Y - ZoomBox.Top) < Margin) and
              (X <= ZoomBox.Right) and (X >= ZoomBox.Left ) then begin
            pbDisplay.Cursor := crSizeNS ;
            MousePos := MTop ;
            end
        else if (Abs(Y - ZoomBox.Bottom) < Margin ) and
               (X <= ZoomBox.Right) and (X >= ZoomBox.Left ) then begin
            pbDisplay.Cursor := crSizeNS ;
            MousePos := MBottom ;
            end
        else if (ZoomBox.Bottom > Y) and (Y > ZoomBox.Top) and
                (ZoomBox.Right > X) and (X > ZoomBox.Left) then begin
            pbDisplay.Cursor := CrSize ;
            MousePos := MDrag ;
            XOld := X ;
            YOld := Y ;
            end
        else
            pbDisplay.Cursor := crDefault ;

        end
     else if MoveZoomBox then begin

          pbDisplay.canvas.DrawFocusRect( ZoomBox ) ;

          { Move the part of the zoom box which is under the mouse }

          case MousePos of
          TopLeft : Begin
              if (ZoomBox.Bottom-Y) > ZoomMin then ZoomBox.Top := Y ;
              if (ZoomBox.Right-X) > ZoomMin then ZoomBox.Left := X ;
             end ;
          TopRight : Begin
              if (ZoomBox.Bottom-Y) > ZoomMin then ZoomBox.Top := Y ;
              if (X - ZoomBox.Left) > ZoomMin then ZoomBox.Right := X ;
              end ;
          BottomLeft : Begin
              if (ZoomBox.Bottom-Y) > ZoomMin then ZoomBox.Top := Y ;
              if (ZoomBox.Right-X) > ZoomMin then ZoomBox.Left := X ;
              end ;
          BottomRight : Begin
              if (ZoomBox.Bottom-Y) > ZoomMin then ZoomBox.Top := Y ;
              if (X - ZoomBox.Left) > ZoomMin then ZoomBox.Right := X ;
              end ;
          MTop : Begin
              if (ZoomBox.Bottom-Y) > ZoomMin then ZoomBox.Top := Y ;
              end ;
          MBottom : Begin
              if (Y - ZoomBox.Top) > ZoomMin then ZoomBox.Bottom := Y ;
              end ;
          MLeft : Begin
              if (ZoomBox.Right-X) > ZoomMin then ZoomBox.Left := X ;
              end ;
          MRight : Begin
              if (X - ZoomBox.Left) > ZoomMin then ZoomBox.Right := X ;

              end ;
          MDrag : begin
              Width := ZoomBox.Right - ZoomBox.Left ;
              Height := ZoomBox.Bottom - ZoomBox.Top ;
              ZoomBox.Left := MaxInt( [ZoomBox.Left + (X - XOld),ZoomCh.Left]) ;
              ZoomBox.Right := MinInt( [ZoomBox.Left + Width,ZoomCh.Right]) ;
              ZoomBox.Left := ZoomBox.Right - Width ;
              ZoomBox.Top := MaxInt( [ZoomBox.Top + (Y - YOld),ZoomCh.Top]) ;
              ZoomBox.Bottom := MinInt( [ZoomBox.Top + Height,ZoomCh.Bottom]) ;
              ZoomBox.Top := ZoomBox.Bottom - Height ;
              XOld := X ;
              YOld := Y ;
              end
          else
          end ;

          { Keep within bounds }

          ZoomBox.Left :=    MaxInt( [ZoomBox.Left,ZoomCh.Left] ) ;
          ZoomBox.Left :=    MinInt( [ZoomBox.Left,ZoomCh.Right] ) ;
          ZoomBox.Right :=   MaxInt( [ZoomBox.Right,ZoomCh.Left] ) ;
          ZoomBox.Right :=   MinInt( [ZoomBox.Right,ZoomCh.Right] ) ;
          ZoomBox.Top :=     MaxInt( [ZoomBox.Top,ZoomCh.Top] ) ;
          ZoomBox.Top :=     MinInt( [ZoomBox.Top,ZoomCh.Bottom] ) ;
          ZoomBox.Bottom :=  MaxInt( [ZoomBox.Bottom,ZoomCh.Top] ) ;
          ZoomBox.Bottom :=  MinInt( [ZoomBox.Bottom,ZoomCh.Bottom] ) ;

          pbDisplay.canvas.DrawFocusRect( ZoomBox ) ;
          end ;

     end ;

procedure TZoomFrm.pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
     MoveZoomBox := False ;
     end;


procedure TZoomFrm.cbChannelsChange(Sender: TObject);
begin

     Channel[ChOnDisplay].xMin := ((ZoomBox.Left - ZoomCh.Left) / ZoomCh.xScale)
                                  + ZoomCh.xMin ;
     Channel[ChOnDisplay].xMax := ((ZoomBox.Right - ZoomCh.Left) / ZoomCh.xScale)
                                  + ZoomCh.xMin ;
     Channel[ChOnDisplay].yMin := ((ZoomCh.Bottom - ZoomBox.Bottom) / ZoomCh.yScale)
                                  + ZoomCh.yMin ;
     Channel[ChOnDisplay].yMax := ((ZoomCh.Bottom - ZoomBox.Top) / ZoomCh.yScale)
                                  + ZoomCh.yMin ;

     { If the channel number has changed re-plot the display }
     pbDisplay.Refresh ;
     end;

procedure TZoomFrm.FormDeactivate(Sender: TObject);
begin
     HeapBuffers( Deallocate ) ;
     end;

procedure TZoomFrm.ZoomOut ;
{ ---------------------------------------------
  Set display channels to minimum magnification
  ---------------------------------------------}
var
   i : Integer ;
begin
     for i := 0 to fH.NumChannels-1 do begin
         Channel[i].xMin := 0. ;
         Channel[i].xMax := fH.NumSamples-1 ;
         Channel[i].yMin := MinADCValue ;
         Channel[i].yMax := MaxADCValue ;
         end ;
    { Refresh child windows that exist }
     with Main do for i := 0 to MDIChildCount-1 do MDICHildren[i].Refresh ;
    end;

procedure TZoomFrm.bOKClick(Sender: TObject);
var
   ch,NumInUse : LongInt ;
   i : Integer ;
begin

     { Update channels in use }
     Channel[0].InUse := ckInUse0.checked ;
     Channel[1].InUse := ckInUse1.checked ;
     Channel[2].InUse := ckInUse2.checked ;
     Channel[3].InUse := ckInUse3.checked ;
     Channel[4].InUse := ckInUse4.checked ;
     Channel[5].InUse := ckInUse5.checked ;
     { Ensure at least one channel is displayed }
     NumInUse := 0 ;
     for ch := 0 to FH.NumChannels-1 do
         if Channel[ch].InUse then NumInUse := NumInUse + 1;
     if NumInUse = 0 then Channel[0].InUse := True ;

     { Update magnification of selected channel }

     Ch := cbChannels.itemindex ;

     Channel[ch].xMin := ((ZoomBox.Left - ZoomCh.Left) / ZoomCh.xScale)
                           + ZoomCh.xMin ;
     Channel[ch].xMax := ((ZoomBox.Right - ZoomCh.Left) / ZoomCh.xScale)
                           + ZoomCh.xMin ;
     Channel[ch].yMin := ((ZoomCh.Bottom - ZoomBox.Bottom) / ZoomCh.yScale)
                           + ZoomCh.yMin ;
     Channel[ch].yMax := ((ZoomCh.Bottom - ZoomBox.Top) / ZoomCh.yScale)
                           + ZoomCh.yMin ;

     { Make sure X axis scaling is the same for all channels }
     for ch := 0 to FH.NumChannels -1 do begin
         Channel[ch].XMin := Channel[cbChannels.itemindex].xMin ;
         Channel[ch].XMax := Channel[cbChannels.itemindex].xMax ;
         end ;
     { Refresh all open windows }
     with Main do
          for i := 0 to MDIChildCount-1 do MDICHildren[i].Refresh ;

     end;


procedure TZoomFrm.FormShow(Sender: TObject);
begin
     HeapBuffers( Allocate ) ;
     end;

procedure TZoomFrm.FormHide(Sender: TObject);
begin
     HeapBuffers( Deallocate ) ;
     end;

procedure TZoomFrm.FormCreate(Sender: TObject);
begin
     BuffersAllocated := False ;
     end;

end.
