// =====================================================
//
//   miniLA_win - Display unit
//
//   (c) miniLA Team
//
// =====================================================
//
// This is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this package; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.




unit uDisplay;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  stdctrls,extctrls, WOwnrScrllBar,
  uLATypes,
  uUtils,
  uDataHold;

procedure PrepareDispData(var DispData:TDisplayData);
function ChannelPosValueToStr(pos: int64; channel: integer):string;
function ChannelValueToStr(value: cardinal; channel: integer):string;

type
  // cursors
  TCursor=class(TGraphicControl)
  protected
    procedure Paint; override;
  public
    Horizontal : boolean;		// horizontal/vertical cursor
    Enabled    : boolean;
    Moving     : boolean;
    Zooming    : boolean;
    Position   : int64;
    Number     : byte;
    ColorC     : TColor;
    constructor Create(AOwner:TComponent; No:byte); reintroduce;
    procedure   MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); reintroduce;
    procedure   MouseUp  (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); reintroduce; dynamic;
    procedure   MouseDn  (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
  end;

  // waveforms
  TLogDisplay=class(TGraphicControl)
  private
    FZoom     : real; 			// zoom (range 100 to MaxZoom [%])
    FMaxZoom  : real;
    FPosition : int64; 			// pocatecni pozice dat ve vzorcich
    FOnMouseMoved  : TNotifyEvent;
    procedure  SetZoom    (Value: real);
    procedure  SetPosition(Value: int64);

  public
    { Public declarations }
    Bitmap    : TBitmap;
    DataSet   : TDisplayData; 		// konfigurace zobrazeni
    DataHold  : TDataHold;		// data routines
    SignalHeight: integer;		// height of signal
    ZoomRatio : single;			// number of data per pixel for given zoom (FZoom)
    BackColor,
    GroupColor,
    ScaleColor: TColor;
    GridColor: TColor;
    constructor Create(AOwner:TComponent); override;
    destructor Destroy; override;
    procedure  SetColors (Back,Group,Scale,Grid:TColor);
    procedure  SetZoomPos(Zoom_:real;Pos_:int64);
    function   PixelToAddr(Position:int64; Point:integer):int64;
    function   CPixelToAddr(Position:int64; Point:integer):int64;
    function   AddrToPixel(Position:int64; Address:int64):integer;
    procedure  Paint; override;
    procedure  Recalc(SX,SY,W,H:integer;Canvas:TCanvas); virtual;
    procedure  ShowScale (SX,SY,W,H:integer;Canvas:TCanvas;Fill:boolean);
    procedure  MouseMove (Shift: TShiftState; X, Y: Integer); override;
  published
    property   Zoom    : real read FZoom     write SetZoom;
    property   MaxZoom : real read FMaxZoom;
    property   Position: int64 read FPosition write SetPosition default 0;
    property   OnMouseMove : TNotifyEvent read FOnMouseMoved  write FOnMouseMoved  default nil;	// redeclaration to TNotifyEvent
  end;

  // LogPanel
  TLogPanel=class(TCustomControl)
  private
    procedure WMGetDlgCode( var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
  protected
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  public
    { Public declarations }
    TimeBase    : double;
    Display     : TLogDisplay;
    Cursors     : array[0..4] of TCursor;
    StatusText  : TLabel;
    SelDataset  : integer;	// number of signal under cursor focus

    constructor Create(AOwner:TComponent); reintroduce;
    destructor  Destroy; override;
    procedure   Paint; reintroduce; virtual;
    procedure   UpdateCursors;
    procedure   SetHeight        (V    : integer);
    procedure   SetZoom          (Value: real);
    procedure   SetPosition      (Value: int64);
    procedure   SetCenterPosition(Value: int64);
    procedure   SetViewRange     (Pos1, Pos2: int64);
    procedure   SetCursorToHalf  (No   : byte);
    procedure   SetCursorPosition(No   : byte; Pos:int64);
    procedure   SetSnapCursorPosition(No   : byte; Pos:int64);
    procedure   SetZoomPos       (Zoom_: real;Pos_:int64);
    procedure   CenterChange	 (go_forward: boolean);
  end;

implementation
uses dlgMain, dlgSetup, dlgChannel, uINIFile;


//==============================================================================
//  TCursor
//==============================================================================

//=================================================================
constructor TCursor.Create(AOwner:TComponent; No:byte);
//=================================================================
begin
  inherited Create(AOwner);
  Horizontal := false;
  Enabled    := true;
  Moving     := false;
  Zooming    := false;
  Number     := No;
  Position   := int64(0);

  OnMouseMove:= MouseMove;
  OnMouseUp  := MouseUp;
  OnMouseDown:= MouseDn;

  if Number<2 then
    Cursor:=crSizeWE;	// cursor <->
end;

//=================================================================
// Mouse moved above the cursor - moving cursor when mouse key
// pressed
//=================================================================
procedure TCursor.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var Pos: integer;
    Own: TLogPanel;
begin
  Own:= (Owner as TLogPanel);

  if Moving then					// changing position of cursor
   begin
     if Horizontal then
      begin
        Pos:=Y+Top-(Height+1) div 2;
        if Pos<(Height+1) div 2 then Pos:=(Height+1) div 2;
        if Pos>Parent.Height-(Height+1) div 2-16 then Pos:=Parent.Height-(Height+1) div 2-16;
        Top:=Pos;
      end
     else
      begin
        Pos:=X+Left-(Width+1) div 2-2;
        if Pos<0 then Pos:=0;
        if Pos>Parent.Width-Width-5 then Pos:=Parent.Width-Width-5;
	Own.SetCursorPosition(Number,Own.Display.CPixelToAddr(Own.Display.Position,Pos));
        if Number<3 then
           frmMain.Scrollbar2.SetCursorPosition(Number,Own.Display.CPixelToAddr(Own.Display.Position,Pos));
      end;
   end;

  Own.Display.MouseMove(Shift,X+Left,Y+Top);		// propagate to display
end;

//=================================================================
// if above cursor and left button - start moving cursor
// right button - open popup menu
// middle button - zoom in
//=================================================================
procedure TCursor.MouseDn(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var P:TPoint;
begin
   if (Button=mbLeft) and (Number<2) and not (Owner as TLogPanel).Cursors[3].Zooming then
    begin
      Moving:=true;
      (Owner as TLogPanel).Cursors[3].Visible:=false;
      (Owner as TLogPanel).Cursors[4].Visible:=false;
    end
   else if (Button=mbMiddle) and not Moving then
    begin
      (Owner as TLogPanel).Cursors[3].Zooming:=true;
      with (Owner as TLogPanel).Cursors[4] do
       begin
	 horizontal:= false;
	 Top       := (Owner as TLogPanel).Cursors[3].Top;
	 Left      := (Owner as TLogPanel).Cursors[3].Left;
	 Width     := (Owner as TLogPanel).Cursors[3].Width;
	 Height    := (Owner as TLogPanel).Cursors[3].Height;
	 Position  := (Owner as TLogPanel).Cursors[3].Position;
       end;
      MouseCapture := true;
      (Owner as TLogPanel).StatusText.Caption:= 'Select zoom area';
    end
   else if (Button=mbRight) then
    begin
      P.x:=X;
      P.y:=Y;
      P:=ClientToScreen(P);
      frmMain.DisplayPopupMenu.Popup(P.X,P.Y);
    end;
end;

//=================================================================
// end of possible cursor move, enable mouse cursors
//=================================================================
procedure TCursor.MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (Owner as TLogPanel).Cursors[3].Zooming then
   begin
      MouseCapture := false;
      with (Owner as TLogPanel).Cursors[4] do
       begin
	 horizontal:= true;
	 Top       := Y;
	 Left      := 2;
	 Height    := 1;
	 width	   := (Parent as TLogPanel).width;
       end;
      (Owner as TLogPanel).Cursors[3].Zooming := false;
      (Owner as TLogPanel).SetViewRange((Owner as TLogPanel).Cursors[3].Position,
					(Owner as TLogPanel).Cursors[4].Position);
   end;
  Moving:=false;
  (Owner as TLogPanel).Cursors[3].Visible:=true;
  (Owner as TLogPanel).Cursors[4].Visible:=true;

  // snap cursor to edges
  if Number<2 then
     (Owner as TLogPanel).SetSnapCursorPosition(Number,Position);
end;

//=================================================================
// Draw cursor
//=================================================================
procedure TCursor.Paint;
var R:TRect;
begin
  with Canvas do begin
    if Horizontal then begin
      Pen.Color:=ColorC;
      MoveTo(0,(Height+1) div 2-1);
      LineTo(Width,(Height+1) div 2-1);
    end
    else begin
      Pen.Color  :=ColorC;
      Brush.Color:= ColorC;
      MoveTo((Width+1) div 2-1,0);
      LineTo((Width+1) div 2-1,Height-Width+1);

      R:=GetClientRect;
      R.Top:=R.Bottom-Width;
      FillRect(R);
    end;
  end;
end;



//==============================================================================
//  TLogDisplay
//==============================================================================

//=================================================================
constructor TLogDisplay.Create;
//=================================================================
var i:integer;
begin
  inherited Create(AOwner);
  FZoom:=100;
  FMaxZoom := 100;
  FPosition:=int64(0);
  SignalHeight:=1;

  for i:=0 to MaxChannels-1 do
     Dataset.Data[i].DispVal:=0;


  DataHold := TDataHold.Create;
  //for i:=0 to MaxDataLength-1 do
  //   DataHold.DataArray[i]:=((i and $FF) shl 16) + (i and $FFFF);
  //DataHold.InitData(MaxDataLength, true);
  DataHold.InitData(MaxDataLength, false);

  Bitmap:=TBitmap.Create;
  ZoomRatio:=1;
  BackColor:=INIFile.ReadInteger('GUI','BACKGROUND_COLOR', clBlack);
  GroupColor:=INIFile.ReadInteger('GUI','GROUP_COLOR', (51*256+185)*256+85);
  ScaleColor:=INIFile.ReadInteger('GUI','SCALE_COLOR', clWhite);
  GridColor:=INIFile.ReadInteger('GUI','GRID_COLOR', $00444444);
end;

//=================================================================
destructor TLogDisplay.Destroy;
//=================================================================
begin
  INIFile.WriteInteger('GUI','BACKGROUND_COLOR', BackColor);
  INIFile.WriteInteger('GUI','GROUP_COLOR', GroupColor);
  INIFile.WriteInteger('GUI','SCALE_COLOR', ScaleColor);
  INIFile.WriteInteger('GUI','GRID_COLOR', GridColor);
  Bitmap.Free;
  inherited Destroy;
end;

//=================================================================
// Mouse moved above the waveform window
// - update position of cursors
// - update signal values
// - update caption
//=================================================================
procedure TLogDisplay.MouseMove(Shift: TShiftState; X, Y: Integer);
var s:string;
    i:int64;
    j,No:integer;
    m:cardinal;
begin

  // cursor coordinate
  i:=X-5;
  if i>Width-10 then i:=Width-10;
  if i<0 then i:=0;

  // cursor position
  i:=CPixelToAddr(FPosition,i);
  if i<0 then i:=0;
  if i>DataHold.MaxPosition then i:=DataHold.MaxPosition;

  // vertical cursor
  (Owner as TLogPanel).SetCursorPosition(3,i);

  // horizontal cursor
  if not((Owner as TLogPanel).Cursors[3].Zooming) then
   begin
     if Y<2 then Y:=2;
     if Y>Height-3 then Y:=Height-3;
     (Owner as TLogPanel).Cursors[4].Top:=Y;

     if DataSet.NumberOfLines=0 then begin
       (Owner as TLogPanel).StatusText.Caption:='All signals are disabled';
       (Owner as TLogPanel).SelDataset := -1;
       exit;
     end;

     s:='Main: '+IntToDecStr(i-(Owner as TLogPanel).Cursors[2].Position)+' ('+TmToStr((i-(Owner as TLogPanel).Cursors[2].Position)/(Owner as TLogPanel).TimeBase,2)+'s)';

     // show signal(+weight)/group value in status text
     No:=(Y-3) div SignalHeight;
     if No<0 then No:=0;
     if No<DataSet.NumberOfLines then
      begin
        (Owner as TLogPanel).SelDataset := No;
        if DataSet.Data[No].Group then
	   s:=s+' - Group: '+DataSet.Data[No].Name+' <'+ChannelPosValueToStr(i, No)+'>'
	else
         begin
           j:=0;
           m:=DataSet.Data[No].Mask;
           while m<>0 do begin
             m:=m shr 1;
             inc(j);
           end;
           s:=s+' - Signal: '+IntToDecStr(j)+'. '+DataSet.Data[No].Name;
	   if DataHold.GetSampleValue(i) and DataSet.Data[No].Mask<>0 then s:=s+' <log. 1>'
                                                   else s:=s+' <log. 0>';
         end;
      end
     else
       (Owner as TLogPanel).SelDataset := -1;

     // print caption
     (Owner as TLogPanel).StatusText.Caption:= s;
   end;


  // set focus to LogPanel (to allow receiving of keyboard strokes)
  if not(Owner as TLogPanel).Focused then
     (Owner as TLogPanel).SetFocus;

  // run OnMouseMove
  if assigned(FOnMouseMoved) then
     FOnMouseMoved(self);
end;

//=================================================================
procedure TLogDisplay.SetZoom(Value: real);
//=================================================================
begin
  SetZoomPos(Value,FPosition);
end;

//=================================================================
procedure TLogDisplay.SetPosition(Value: int64);
//=================================================================
begin
  SetZoomPos(FZoom,Value);
end;


//=================================================================
// Set new group/background colors
//=================================================================
procedure TLogDisplay.SetColors(Back,Group,Scale,Grid:TColor);
begin
  if    (Back<>BackColor) or (Group<>GroupColor)
     or (Scale<>ScaleColor) or (Grid<>GridColor) then
   begin
     BackColor:=Back;
     GroupColor:=Group;
     ScaleColor:=Scale;
     GridColor:=Grid;
     Recalc(0,0,Width,Height,Bitmap.Canvas);
     Paint;
   end;
end;


//=================================================================
// Redraw the waveform with given zoom and data position
//=================================================================
procedure TLogDisplay.SetZoomPos(Zoom_:real;Pos_:int64);
var s:string;
begin
  // check ranges
  if      Zoom_ > MaxZoom then Zoom_:= MaxZoom
  else if Zoom_ < 100     then Zoom_:= 100;

  if      Pos_ > DataHold.MaxPosition then Pos_:= DataHold.MaxPosition
  else if Pos_ < 0            then Pos_:= 0;

  if (Zoom_<>FZoom) or (Pos_<>FPosition) then begin
    FZoom := round(Zoom_);
    FPosition := Pos_;

    ZoomRatio:=100*DataHold.MaxPosition/(Width-10)/FZoom;		           // data/pixel for given zoom

    if Round((Width-10)*ZoomRatio)+FPosition > DataHold.MaxPosition then
      FPosition:=Round(DataHold.MaxPosition-(Width-10)*ZoomRatio);
    if FPosition<0 then FPosition:=0;

    Recalc(0,0,Width,Height,Bitmap.Canvas);
    Paint;

    // update position info
    if frmMain.ScrollBar2.Enabled then
     begin
       if frmMain.ScrollBar2.Max=frmMain.ScrollBar2.PageSize then
          s:='100'
       else
          str(frmMain.ScrollBar2.Position/(frmMain.ScrollBar2.Max-frmMain.ScrollBar2.PageSize)*100:3:1,s);
       s:=s+'%';
     end
    else
       s:='0.0%';
    (Owner as TLogPanel).StatusText.Caption:='Position: '+s;

  end;

end;


//=================================================================
//
//=================================================================
procedure TLogDisplay.Paint;
begin
  if (Width<>Bitmap.Width) or (Bitmap.Height<>Height) then begin
    Bitmap.Width := Width;
    Bitmap.Height:= Height;
    Recalc(0,0,Width,Height,Bitmap.Canvas);
  end;
  Canvas.CopyRect(Canvas.ClipRect,Bitmap.Canvas,Canvas.ClipRect);
end;

//=================================================================
// Return sample number (data offset) for given pixel (x-coordinate)
// left align
//=================================================================
function TLogDisplay.PixelToAddr(Position:int64; 	// zacatek zobrazovanych dat
                                 Point:integer     	// bod na obrazovce vztazeny k 0
                                 ):int64;		// navratova hodnota - index do pole
begin
  Result:= Trunc(Point*ZoomRatio)+Position;
  if Result>DataHold.MaxPosition then Result:=DataHold.MaxPosition;
end;

//=================================================================
// Return sample number (data offset) for given pixel (x-coordinate)
// center align (used for mouse cursor)
//=================================================================
function TLogDisplay.CPixelToAddr(Position:int64; 	// zacatek zobrazovanych dat
                                  Point:integer     	// bod na obrazovce vztazeny k 0
                                  ):int64;        	// navratova hodnota - index do pole
begin
  Result:= Round(Point*ZoomRatio)+Position;
  if Result>DataHold.MaxPosition then Result:=DataHold.MaxPosition;
end;

//=================================================================
// Return pixel (x-coordinate) for given sample number (data offset)
//=================================================================
function TLogDisplay.AddrToPixel(Position:int64; 	// zacatek zobrazovanych dat
                                 Address:int64   	// sample number
                                 ):integer;        	// navratova hodnota - x coordinate
var px: real;
begin
  px:=trunc((Address-Position)/ZoomRatio);
  if px<low(integer) then
     px := low(integer);
  if px>high(integer) then
     px := high(integer);
  Result:=trunc(px);
end;


//=================================================================
// Draw waveforms
//=================================================================
procedure TLogDisplay.Recalc(SX,SY,W,H:integer;Canvas:TCanvas);
{$J+} // needed to enable writes to constant
const InPaint : boolean = false;
{$J-}
var
   smp_n	: real;
   ch		: integer;
   ST,SM,SB	: integer;
   smp_idx	: int64;
   prev_smp_idx	: int64;
   max_smp_idx	: int64;
   act_smp	: cardinal;
   prev_smp	: cardinal;
   prev_x	: integer;
   act_x	: integer;
   d_x		: integer;
   change_found	: boolean;
   chan_val	: string;
   chan_val_size: tsize;
   R		: TRect;
   oldNotify	:TNotifyEvent;
begin
   if InPaint then exit;
   InPaint := true;
   try
      // background
      with Canvas do
       begin
	 Pen.Color:=BackColor;
	 Brush.Color:=BackColor;
	 //R:=GetClientRect;	// note: not used because of different area during printing
	 //Rectangle(R.Left,R.Top,R.Right,R.Bottom);
         Rectangle(SX,SY,W,H);
       end;

      if W<11 then exit;
      if DataSet.NumberOfLines=0 then exit;


      FMaxZoom := DataHold.MaxPosition * 15;         	// max. zoom - 15% of width per sample
      if FMaxZoom < 100 then
         FMaxZoom := 100;
      if FZoom > FMaxZoom then
         FZoom := FMaxZoom;

      ZoomRatio:=100*DataHold.MaxPosition/FZoom/(W-10);			// = samples per pixel for zoom FZoom

      SignalHeight := (H-10-ScaleHeight) div DataSet.NumberOfLines;
      if SignalHeight > -MaxSignalHeight*Canvas.Font.Height then
	 SignalHeight := round(-MaxSignalHeight*Canvas.Font.Height);


      // Horizontal scroller
      if (frmMain.ScrollBar2<>nil) then
         if FZoom>100 then
	  begin
            smp_n:=ZoomRatio*(W-10);					// number of samples on screen
            frmMain.ScrollBar2.Enabled:=true;
	    frmMain.ScrollBar2.Min:=0;
            frmMain.ScrollBar2.PageSize:=0;				// prevent exceptions when setting max.value
	    frmMain.ScrollBar2.Max:=DataHold.MaxPosition;

            // change position without OnChange event
	    oldNotify := frmMain.ScrollBar2.OnChange;
            frmMain.ScrollBar2.OnChange := nil;
	    frmMain.ScrollBar2.Position:=FPosition;
	    frmMain.ScrollBar2.OnChange := oldNotify;

	    frmMain.ScrollBar2.SmallChange:=Round(0.1*smp_n); 		// 10% of screen width
            frmMain.ScrollBar2.LargeChange:=Round(0.8*smp_n); 		// 80% of screen width
	    frmMain.ScrollBar2.PageSize:=Round(smp_n);			// 100% of screen width
	  end
         else
          begin
            frmMain.ScrollBar2.Enabled:=false;
	    frmMain.ScrollBar2.Min:=0;
	    frmMain.ScrollBar2.Max:=DataHold.MaxPosition;
            frmMain.ScrollBar2.Position:=0;
            frmMain.ScrollBar2.PageSize:=frmMain.ScrollBar2.Max;
	    FPosition:=0;
	  end;

      // Zoom ruler position
      frmMain.tbZoom.Max:=IntLog2(round(FMaxZoom / 100));
      frmMain.tbZoom.LineSize:=(IntLog2(round(FMaxZoom / 100)) div 10);
      frmMain.tbZoom.Frequency:=(IntLog2(round(FMaxZoom / 100)) div 10);
      frmMain.tbZoom.PageSize:=(IntLog2(round(FMaxZoom / 100)) div 10);
      frmMain.tbZoomUpdatePosition(IntLog2(round(FZoom / 100)));

      // draw scale
      ShowScale(SX,SY,W,H,Canvas,false);

      // draw waves
      with Canvas do
       begin
	 ST:=3;							// top (y-coordinate)
	 SB:=ST + SignalHeight*8 div 10;			// bottom (y-coordinate)
	 SM:=(ST+SB) div 2;					// middle (y-coordinate)

	 for ch:=0 to DataSet.NumberOfLines-1 do
	  begin

	    // horizontal grid line
	    if frmSetup.ShowGrid then
	     begin
	       Pen.Color := GridColor;
	       MoveTo(SX+5, SignalHeight+ST-3);
	       LineTo(SX+W-10, SignalHeight+ST-3);
	     end;

	    if DataSet.Data[ch].Group then
	       Pen.Color := GroupColor
	    else
	       Pen.Color:=DataSet.Data[ch].Color;

	    act_x := 0;
	    prev_x := -1;					// 0 = change at x(0), -1 = no change at x(0)
	    smp_idx:=PixelToAddr(FPosition,0);			// beginning of displayed data
	    max_smp_idx := PixelToAddr(FPosition,W-10)+1;	// last data index (+1 makes the last sample drawn)

	    // determine whether draw change at x(0)
	    if smp_idx>0 then
	     begin
	       prev_smp := DataHold.GetSampleValue(smp_idx-1) and DataSet.Data[ch].Mask;
	       act_smp := DataHold.GetSampleValue(smp_idx) and DataSet.Data[ch].Mask;
	       if prev_smp <> act_smp then
		  prev_x := 0;
	       prev_smp := act_smp;
             end
            else
	       prev_smp := DataHold.GetSampleValue(smp_idx) and DataSet.Data[ch].Mask;

            // draw
	    repeat
	       change_found := DataHold.FindChange(DataSet.Data[ch].Mask, max_smp_idx, true);
	       act_smp := DataHold.Sample;

	       act_x := AddrToPixel(FPosition, DataHold.Position);
	       if (act_x<>prev_x) then
		begin
		  if (act_x >= W-10) then
                     act_x := W-10;


		  // group
		  if DataSet.Data[ch].Group then
		   begin
                     d_x := (act_x - prev_x) div 2;
                     if (d_x)>2 then
                        d_x := 2;

                     // = or <
                     if prev_x>=0 then
                      begin
                        MoveTo(SX+prev_x+d_x+5,SY+SB);
                        LineTo(SX+prev_x+0+5,SY+SM);
                        LineTo(SX+prev_x+d_x+5,SY+ST);
                        prev_x := prev_x+d_x;
                      end
                     else
                        prev_x:=0;

                     // = or =>
                     if not(change_found) then
                      begin
			MoveTo(SX+prev_x+5,SY+SB);
                        LineTo(SX+act_x+5,SY+SB);		// _
                        MoveTo(SX+prev_x+5,SY+ST);
                        LineTo(SX+act_x+5,SY+ST);		// -
                      end
                     else
                      begin
                        MoveTo(SX+prev_x+5,SY+SB);
                        LineTo(SX+act_x-d_x+5,SY+SB); 		// _
                        LineTo(SX+act_x+5,SY+SM);		// /
                        LineTo(SX+act_x-d_x+5,SY+ST);		// \
                        LineTo(SX+prev_x+5,SY+ST);		// -
                      end;

		     chan_val := ChannelValueToStr(prev_smp, ch);
		     chan_val_size := TextExtent(chan_val);
		     if     ((chan_val_size.cx+2) < (act_x-prev_x))	// enough space for text?
			and ((chan_val_size.cy) < (SB-ST))then
			TextOut(SX+(2+prev_x)+5,
				SY+((ST+SB) div 2) - (chan_val_size.cy div 2),
				chan_val);
		   end
		  else

		  // single signal
		   begin
                     if prev_x<0 then
                        prev_x:=0;

                     if change_found then
                      begin					// vertical line
                        MoveTo(SX+act_x+5,SY+ST);
                        LineTo(SX+act_x+5,SY+SB);
                      end;

		     if prev_smp = 0 then
		      begin					// draw 0
			MoveTo(SX+prev_x+5,SY+SB);
			LineTo(SX+act_x+5,SY+SB);
		      end
		     else
		      begin					// draw 1
			MoveTo(SX+prev_x+5,SY+ST);
			LineTo(SX+act_x+5,SY+ST);
		      end;
		   end;

		end;
	       prev_x := act_x;
	       prev_smp := act_smp;
	    until not(change_found);
	    inc(ST,SignalHeight);
	    inc(SB,SignalHeight);
	    inc(SM,SignalHeight);

	  end;{ch}
       end;{canvas}

   finally
      InPaint:=false;
   end;
end;

//=================================================================
// Draw scale
//=================================================================
procedure TLogDisplay.ShowScale(SX,SY,W,H:integer;Canvas:TCanvas;Fill:boolean);
var i,ax,tx,ty:integer;
    ast,wst:int64;
    s:string;
    R:TRect;
begin
   with Canvas do
    begin
      // draw scale
      if Fill then begin
	Brush.Color:=BackColor;
	R.Top:=H-5-ScaleHeight;
	R.Left:=1;
	R.Bottom:=H;
	R.Right:=W;
	FillRect(R);
      end;

      Pen.Color:=ScaleColor;
      Font.Color:=ScaleColor;

      // horizontal line
      MoveTo(SX+5,SY+H-ScaleHeight-5);
      LineTo(SX+W-5,SY+H-ScaleHeight-5);

      // print sample numbers
      wst:=GetDecades(round((W-10)*ZoomRatio));	   	// calculate the step
      ast:= ((FPosition-(Owner as TLogPanel).Cursors[2].Position) div wst) * wst;  // round to nearest step sample index
      ax := 0;
      while (ax <= W-10) do
       begin
         ax := AddrToPixel(FPosition, ast+(Owner as TLogPanel).Cursors[2].Position);
	 if (ax >= 0) and (ax <= W-10) then
	  begin
	    // vertical line
            Pen.Color:=ScaleColor;
	    MoveTo(SX+5+ax,SY+H-ScaleHeight);
 	    LineTo(SX+5+ax,SY+H-ScaleHeight-8);

            // vertical grid line
            if frmSetup.ShowGrid then
             begin
               Pen.Color:=GridColor;
               LineTo(SX+5+ax,SY);
             end;
             
	    // psample number
	    s:=IntToDecStr(ast);
	    tx:=5+ax-(TextWidth(s) div 2);
	    if tx<5 then tx:=5;
	    if tx+TextWidth(s)>W-5 then tx:=W-5-TextWidth(s);
            ty := SY+H-ScaleHeight+1;
            if ty+TextHeight('0') > H then
               ty := H-TextHeight('0');
	    TextOut(SX+tx,SY+ty,s);
	  end;
	 ast := ast+wst;
       end;

      // draw small vertical lines
      Pen.Color:=ScaleColor;
      if ZoomRatio<0.5 then
	for i:=0 to W-10 do
	  if PixelToAddr(FPosition,i)<>PixelToAddr(FPosition,i+1) then // next sample?
           begin
	     MoveTo(SX+5+i,SY+H-ScaleHeight-8);
	     LineTo(SX+5+i,SY+H-ScaleHeight-5);
	   end;
    end;
end;


//==============================================================================
//  TLogPanel
//  - create window
//  - create cursors
//==============================================================================
constructor TLogPanel.Create(AOwner:TComponent);
var h,w: integer;
begin
  inherited Create(AOwner);
  Height:=320;
  h:= Height;
  align:=alTop;
  TabStop := true;

  Display       := TLogDisplay.Create(self);
  Display.Parent:= Self;
  Display.Align := alTop;
  Display.Height:= Height-23;

  // trigger cursor
  Cursors[2]:=TCursor.Create(self,2);
  with Cursors[2] do begin
    Parent    := self;
    Horizontal:= false;
    Top       := 2;
    Left      := 5;
    Width     := 1;
    Height    := h-25;
    Position  := 0;
    ColorC    := INIFile.ReadInteger('GUI','CURSOR2_COLOR', clRed);
  end;
  frmMain.Scrollbar2.SetCursorColor(2, Cursors[2].ColorC);

  // mouse cursor - vertical
  Cursors[3]:=TCursor.Create(self,3);
  with Cursors[3] do begin
    Parent    := self;
    Horizontal:= false;
    Top       := 2;
    Left      := 4;
    Width     := 1;
    Height    := h-25;
    ColorC    := INIFile.ReadInteger('GUI','CURSOR3_COLOR', clAqua);
  end;

  // mouse cursor - horizontal
  Cursors[4]:=TCursor.Create(self,4);
  with Cursors[4] do begin
    Parent    := self;
    Horizontal:= true;
    Top       := h div 2;
    Left      := 2;
    Height    := 1;
    ColorC    := INIFile.ReadInteger('GUI','CURSOR4_COLOR', clAqua);
  end;

  // cursor 1
  Cursors[0]:=TCursor.Create(self,0);
  with Cursors[0] do begin
    Parent    := self;
    Horizontal:= false;
    Top       := 2;
    Left      := 20;
    Width     := 3;
    Height    := h-20;
    ColorC    := INIFile.ReadInteger('GUI','CURSOR0_COLOR', clBlue);
  end;
  frmMain.Scrollbar2.SetCursorColor(0, Cursors[0].ColorC);

  // cursor 2
  Cursors[1]:=TCursor.Create(self,1);
  with Cursors[1] do begin
    Parent    := self;
    Horizontal:= false;
    Top       := 2;
    Left      := 40;
    Width     := 3;
    Height    := h-24;
    ColorC    := INIFile.ReadInteger('GUI','CURSOR1_COLOR', clBlue);
  end;
  frmMain.Scrollbar2.SetCursorColor(1, Cursors[1].ColorC);


  StatusText:=TLabel.Create(self);
  with StatusText do begin
    Parent     := self;
    Top        := h-15;
    Left       := 2;
    Caption    := 'Position: 0.0%';
    AutoSize   := true;
    Transparent:= true;
  end;

  TimeBase:=100e6;
end;

//=================================================================
destructor TLogPanel.Destroy;
//=================================================================
var i:integer;
begin
  // store colors of cursors
  for i:=0 to 4 do
   begin
     INIFile.WriteInteger('GUI','CURSOR'+IntToStr(i)+'_COLOR', Cursors[i].ColorC);
     Cursors[i].Destroy;
   end;
  inherited Destroy;
end;


//=================================================================
procedure TLogPanel.SetHeight(V: integer);
//=================================================================
begin
  Height:= V;
  Display.Height   := Height-23;
  Cursors[0].Height:= Height-20;
  Cursors[1].Height:= Height-17;
  Cursors[3].Height:= Height-27;
  Cursors[2].Height:= Height-27;
  Cursors[4].Width := Width-4;
  StatusText.Top   := Height-15;
end;

//=================================================================
// Update cursors to new positions
//=================================================================
procedure TLogPanel.UpdateCursors;
begin
  SetCursorPosition(0,Cursors[0].Position);
  frmMain.ScrollBar2.SetCursorPosition(0,Cursors[0].Position);
  SetCursorPosition(1,Cursors[1].Position);
  frmMain.ScrollBar2.SetCursorPosition(1,Cursors[1].Position);
  SetCursorPosition(2,Cursors[2].Position);
  frmMain.ScrollBar2.SetCursorPosition(2,Cursors[2].Position);
  if Cursors[3].Zooming then
     SetCursorPosition(4,Cursors[4].Position);
  Cursors[0].Repaint;
  Cursors[1].Repaint;
  Cursors[2].Repaint;
  Cursors[3].Repaint;
  Cursors[4].Repaint;
end;

//=================================================================
procedure TLogPanel.Paint;
//=================================================================
begin
   UpdateCursors;
end;

//=================================================================
// Set new zoom (keeps center position)
//=================================================================
procedure TLogPanel.SetZoom(Value: real);
var npos: integer;
begin
   if (Display.Zoom<>Display.MaxZoom) or (Value<Display.MaxZoom) then
    begin
      with display do
         // calculate position to keep center position unchanged 
	 npos := round(Position + 50*(DataHold.MaxPosition/FZoom - DataHold.MaxPosition/Value));
      SetZoomPos(Value, npos);
    end;
end;

//=================================================================
procedure TLogPanel.SetPosition(Value: int64);
//=================================================================
begin
   SetZoomPos(Display.Zoom, Value);
end;

//=================================================================
procedure TLogPanel.SetCenterPosition(Value: int64);
//=================================================================
var npos: integer;
begin
   with display do
      npos := round(Value - 50*(DataHold.MaxPosition/FZoom));
   SetZoomPos(Display.Zoom, npos);
end;

//=================================================================
procedure TLogPanel.SetZoomPos(Zoom_: real; Pos_:int64);
//=================================================================
begin
   Display.SetZoomPos(Zoom_,Pos_);
   UpdateCursors;
end;


//=================================================================
procedure TLogPanel.SetViewRange(Pos1, Pos2: int64);
//=================================================================
var Z,P:int64;
begin
   Z:=Pos2-Pos1;
   if Z<0 then P:=Pos2
	  else P:=Pos1;
   Z:=Abs(Z);
   if Z=0 then Z:=round(Display.Zoom)
          else Z:=round(90*Display.DataHold.MaxPosition / Z);

   if Z>Display.MaxZoom then Z:=round(Display.MaxZoom);

   dec(P,5*Display.DataHold.MaxPosition div Z);
   SetZoomPos(Z,P);
end;

//=================================================================
// Set cursor number No into the middle of the screen
//=================================================================
procedure TLogPanel.SetCursorToHalf(No: byte);
begin
  case No of
    0: begin
         SetCursorPosition(0, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
         frmMain.Scrollbar2.SetCursorPosition(0, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
       end;
    1: begin
         SetCursorPosition(1, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
         frmMain.Scrollbar2.SetCursorPosition(1, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
       end;
    2: begin
         SetCursorPosition(2, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
         frmMain.Scrollbar2.SetCursorPosition(2, (Display.Position+Display.PixelToAddr(Display.Position,Width-10)) div 2);
       end;
  end;
end;

//=================================================================
// Set position of cursor No to Pos (pos=sample number)
//=================================================================
procedure TLogPanel.SetCursorPosition(No: byte; Pos:int64);
var i:int64;
begin
  if (No<3) or ((No=4) and Cursors[No].Zooming) then begin
    if Pos>Display.DataHold.MaxPosition then
      Pos:=Display.DataHold.MaxPosition;
    // show cursor on the screen? (cursor within display range?)
    Cursors[No].Visible:=not ((Pos<Display.Position) or (Pos>Display.PixelToAddr(Display.Position,Width-10)));
  end;

  // calculate cursor position
  i:=Display.AddrToPixel(Display.Position, pos);
  Cursors[No].Left:=i+5-(Cursors[No].Width) div 2;
  Cursors[No].Position:=Pos;

  // trigger cursor
  if No=2 then begin
    Display.ShowScale(0,0,Display.Width,Display.Height,Display.Canvas,true);
    with Display.Canvas.ClipRect do begin
      Left  := 0;
      Right := Display.Width;
      Bottom:= Display.Height;
      Top   := Bottom-5-ScaleHeight;
    end;
    Display.Paint;
    Cursors[0].Repaint;
    Cursors[1].Repaint;
  end;
  Cursors[No].Repaint;

  // update status bar
  case No of
    0,
    1: begin
       end;
    2: frmMain.StatusBar1.Panels[1].Text:='Trigger: '+IntToDecStr(Pos)+' ('+TmToStr(Pos/TimeBase,2)+'s)';
  end;

  frmMain.StatusBar1.Panels[2].Text:='1: '+IntToDecStr(Cursors[0].Position-Cursors[2].Position)+
    ' ('+TmToStr((Cursors[0].Position-Cursors[2].Position)/TimeBase,2)+'s)';
  frmMain.StatusBar1.Panels[3].Text:='2: '+IntToDecStr(Cursors[1].Position-Cursors[2].Position)+
    ' ('+TmToStr((Cursors[1].Position-Cursors[2].Position)/TimeBase,2)+'s)';

  i:=Cursors[1].Position-Cursors[0].Position;
  frmMain.StatusBar1.Panels[4].Text:='Diff.: '+IntToDecStr(i)+' ('+TmToStr(i/TimeBase,2)+'s)';
end;

//=================================================================
// Set position of cursor No to Pos (pos=sample number)
// and snap it to edge in range
//=================================================================
procedure TLogPanel.SetSnapCursorPosition(No: byte; Pos:int64);
var
   i,j : integer;
   mpos,lpos,rpos: int64;
   val: cardinal;
   mask: cardinal;
begin
  if No>1 then
     raise Exception.Create('Snapping works only with cursor 0/1');

  if Pos>Display.DataHold.MaxPosition then
    Pos:=Display.DataHold.MaxPosition;

  // calculate cursor position
  i:=Display.AddrToPixel(Display.Position, pos);
  if (i>5) and (i<Width-10) and (SelDataset>=0) then
   begin
     mask := Display.Dataset.Data[SelDataset].Mask;

     // left edge
     j:=i-SnapDistance;
     if j<5 then
	j:=5;

     mpos := Display.PixelToAddr(Display.Position,j);
     val := Display.DataHold.GetSampleValue(pos) AND Mask;
     lpos := -1;

     if Display.DataHold.FindChange(Mask, mpos, false) then
	lpos := Display.DataHold.Position+1;

     // right edge
     j:=i+SnapDistance;
     if j>Width-10 then
	j:=Width-10;

     mpos := Display.PixelToAddr(Display.Position,j);
     val := Display.DataHold.GetSampleValue(pos) AND Mask;	// reinitialize pointer (start of search)
     rpos := -1;

     if Display.DataHold.FindChange(Mask, mpos, true) then
	rpos := Display.DataHold.Position;

     // determine distance to nearest edge
     if (lpos=-1) and (rpos=-1) then
	pos := pos				// no change found
     else if (lpos=-1) then
	pos := rpos
     else if (rpos=-1) then
	pos := lpos
     else if (rpos-pos)>(pos-lpos) then
	pos := lpos
     else
	pos := rpos;

   end;

  SetCursorPosition(No, Pos);

end;


//=================================================================
// Find next/previous change of the signal and center the screen
//=================================================================
procedure TLogPanel.CenterChange(go_forward: boolean);
var
   pos: int64;
   val: cardinal;
   mask: cardinal;
begin
   if SelDataset < 0 then
      exit;

   pos:= Cursors[3].Position;
   mask := Display.Dataset.Data[SelDataset].Mask;
   val := (Display.DataHold.GetSampleValue(pos) AND Mask);	// seek to sample position

   if go_forward then
    begin
      if Display.DataHold.FindChange(Mask, Display.DataHold.MaxPosition, true) then
	 SetCenterPosition(Display.DataHold.Position);
    end
   else
    begin
      if Display.DataHold.FindChange(Mask, 0, false) then
	 SetCenterPosition(Display.DataHold.Position+1);
    end;

end;



//=================================================================
// enable reception of cursor keys keydown events
//=================================================================
procedure TLogPanel.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
   msg.result:= DLGC_WANTARROWS;
end;



//=================================================================
procedure TLogPanel.KeyDown(var Key: Word; Shift: TShiftState);
//=================================================================
var
   smp_n	: real;
begin
   if key in [VK_LEFT, VK_RIGHT, VK_HOME, VK_END] then
    begin
      smp_n:=Display.ZoomRatio*(Display.Width-10);

      case Key of
        VK_LEFT:
          begin
            if (ssAlt in Shift) then
               SetPosition(Display.Position-1)
            else if (ssCtrl in Shift) then
               SetPosition(Display.Position-Round(0.8*smp_n))
            else
               SetPosition(Display.Position-Round(0.1*smp_n));
          end;
        VK_RIGHT:
          begin
            if (ssAlt in Shift) then
               SetPosition(Display.Position+1)
            else if (ssCtrl in Shift) then
               SetPosition(Display.Position+Round(0.8*smp_n))
            else
               SetPosition(Display.Position+Round(0.1*smp_n));
          end;
        VK_HOME:
          SetPosition(0);
        VK_END:
          SetPosition(Display.DataHold.MaxPosition-Round(smp_n));
      end;
   end;
   Key := 0;
end;


//=================================================================
// Fill TDispData with names from Signal/Bus definitions (frmChannel)
//=================================================================
procedure PrepareDispData(var DispData:TDisplayData);
var i,j,k,channel:integer;
    mask:cardinal;
    s:string;
begin
  channel:=0;

  with frmChannel do
   begin
     FillChar(DispData,SizeOf(DispData),0); // clear target variable

     // signal groups
     for i:=0 to lbGroups.Items.Count-1 do
       with (lbGroups.Items.Objects[i] as TGroupList) do
	 if Count>0 then
	  begin // insert only non-empty group
	    DispData.Data[channel].Base:=Base;
	    DispData.Data[channel].Name:=lbGroups.Items.Strings[i];
	    DispData.Data[channel].Group:=true;
	    DispData.Data[channel].Color:=clSilver;	// later gets predefined (in Recalc)
	    mask:=0;
	    for j:=0 to Count-1 do
	     begin // contantenate channel bits
	       s:=Strings[j];
	       s:=Copy(s,1,Pos('.',s)-1);
	       k:=StrToInt(s)-1;
	       mask:=mask or (1 shl k); // v masce mame bitovou masku channelu
	       DispData.Data[channel].Signals[j]:=k; // signal number
	     end;
	    DispData.Data[channel].Mask:=mask;
	    DispData.Data[channel].NumberOfSignals:=Count;
	    inc(channel);
	  end;

     // single signals
     for i:=0 to MaxChannels-1 do
       with ChannelInfo[i] do
	 if (Group.ItemIndex=0) and Enabled.Checked and Enabled.Enabled then
	  begin
	    DispData.Data[channel].Color:=Edit.Font.Color;
	    DispData.Data[channel].Name:=Edit.Text;
	    DispData.Data[channel].Mask:=1 shl i;
	    DispData.Data[channel].NumberOfSignals:=1;
	    DispData.Data[channel].Signals[0]:=i;
	    inc(channel);
	  end;
     DispData.NumberOfLines:=channel;
   end;
end;

//==============================================================================
//  Miscellaneous
//==============================================================================

//=================================================================
// Return value of signal/channel in string format
//=================================================================
function ChannelPosValueToStr(pos: int64; channel: integer):string;
var
    val  : cardinal;
begin
   val := frmMain.LogPanel.Display.DataHold.GetSampleValue(pos);
   result := ChannelValueToStr(val, channel);
end;

function ChannelValueToStr(value: cardinal; channel: integer):string;
var
    j    : integer;
    Mask : cardinal;
    res  : cardinal;
begin
   with frmMain.LogPanel.Display do  // Value
    begin
      mask := Dataset.Data[channel].Mask;
      if Dataset.Data[channel].Group then
       begin
	 res:=0;
	 for j:=0 to Dataset.Data[channel].NumberOfSignals-1 do
	   if (value and (1 shl Dataset.Data[channel].Signals[j])) <> 0 then
	      res:=res or (1 shl j);

	 case Dataset.Data[channel].Base of
	   0: result:= IntToBin(res,Dataset.Data[channel].NumberOfSignals, false)+'b';// binary
	   1: result:= IntToOct(res,Dataset.Data[channel].NumberOfSignals)+'o';// oct
	   2: result:= IntToStr(res);// dec
	   3: result:= IntToHex(res,Dataset.Data[channel].NumberOfSignals)+'h';// hex
	 end;
       end
      else if (value and Mask) <> 0 then
	  result:= '1'
      else
	  result:= '0';
      end;
 end;


end.
