unit uDlgComponents;

interface

uses
  SysUtils, Windows, Messages, Classes, Controls, StdCtrls, ComCtrls, ExtCtrls, uReminders,
  TypInfo, StrUtils, ORCtrls, ORDtTm, Forms, Graphics, Dialogs, RTLConsts, Buttons,
  VA508AccessibilityManager;

type
  ICPRSDialogComponent = interface(IInterface)
  ['{C4DBA10A-2A8D-4F71-AE20-5BA350D48551}']
    function Component: TControl;
    function GetBeforeText: string;
    procedure SetBeforeText(Value: string);
    function GetAfterText: string;
    procedure SetAfterText(value: string);
    function GetRequiredField: boolean;
    procedure SetRequiredField(Value: boolean);
    function AccessText: string;
    function GetSRBreak: boolean;
    procedure SetSRBreak(Value: boolean);
    property BeforeText: string read GetBeforeText write SetBeforeText;
    property AfterText: string read GetAfterText write SetAfterText;
    property SRBreak: boolean read GetSRBreak write SetSRBreak;
    property RequiredField: boolean read GetRequiredField write SetRequiredField;
  end;

  TCPRSDialogComponent = class(TInterfacedObject, ICPRSDialogComponent)
  private
    FRequiredField: boolean;
    FComponent: TControl;
    FBeforeText: string;
    FAfterText: string;
    FSRBreak: boolean;
    FComponentName: string;
    FFollowOnCaption: string;
  public
    constructor Create(AComponent: TControl; AComponentName: string;
                       FollowOnCaption: string = '');
    function Component: TControl;
    function GetBeforeText: string;
    procedure SetBeforeText(Value: string);
    function GetAfterText: string;
    procedure SetAfterText(value: string);
    function GetRequiredField: boolean;
    procedure SetRequiredField(Value: boolean);
    function AccessText: string;
    function GetSRBreak: boolean;
    procedure SetSRBreak(Value: boolean);
    property BeforeText: string read GetBeforeText write SetBeforeText;
    property AfterText: string read GetAfterText write SetAfterText;
    property SRBreak: boolean read GetSRBreak write SetSRBreak;
    property RequiredField: boolean read GetRequiredField write SetRequiredField;
  end;

  TCPRSDialogStaticLabel = class(TVA508StaticText);

  TCPRSDialogParentCheckBox = class(TORCheckBox)
  private
    FAccessText: string;
  public
    property AccessText: string read FAccessText write FAccessText;
  end;

  TCPRSDialogFieldEdit = class(TEdit, ICPRSDialogComponent)
  private
    FCPRSDialogData: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogData implements ICPRSDialogComponent;
  end;

  TCPRSDialogComboBox = class(TORComboBox, ICPRSDialogComponent)
  private
    FCPRSDialogData: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogData implements ICPRSDialogComponent;
  end;

  TCPRSDialogCheckBox = class(TORCheckBox, ICPRSDialogComponent)
  private
    FCPRSDialogData: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogData implements ICPRSDialogComponent;
  end;

  TCPRSDialogButton = class(TButton, ICPRSDialogComponent)
  private
    FCPRSDialogData: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogData implements ICPRSDialogComponent;
  end;

  TCPRSDialogYearEdit = class(TORYearEdit);

  TCPRSDialogDateCombo = class(TORDateCombo, ICPRSDialogComponent)
  private
    FCPRSDialogInfo: ICPRSDialogComponent;
  protected
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
  end;

  TCPRSDialogDateBox = class(TORDateBox, ICPRSDialogComponent)
  private
    FCPRSDialogInfo: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
  end;

  TCPRSNumberField = class(TEdit);

  TCPRSDialogNumber = class(TPanel, ICPRSDialogComponent)
  private
    FCPRSDialogInfo: ICPRSDialogComponent;
    FEdit: TCPRSNumberField;
    FUpDown: TUpDown;
  public
    constructor CreatePanel(AOwner: TComponent);
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
    property Edit: TCPRSNumberField read FEdit;
    property UpDown: TUpDown read FUpDown;
  end;

  TCPRSTemplateFieldLabel = class(TCPRSDialogStaticLabel)
  private
    FExclude: boolean;
  public
    property Exclude: boolean read FExclude write FExclude;
  end;

  TCPRSDialogHyperlinkLabel = class(TCPRSTemplateFieldLabel, ICPRSDialogComponent)
  private
    FURL: string;
    FCPRSDialogInfo: ICPRSDialogComponent;
  protected
    procedure Clicked(Sender: TObject);
    procedure KeyPressed(Sender: TObject; var Key: Char);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Init(Addr: string);
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
  end;

  TCPRSDialogRichEdit = class(TRichEdit, ICPRSDialogComponent)
  private
    FCPRSDialogData: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogData implements ICPRSDialogComponent;
  end;

{This is the panel associated with child items in template and reminders dialogs}
  TDlgFieldPanel = class(TPanel)
  private
    FOnDestroy: TNotifyEvent;
    FCanvas: TControlCanvas;    {used to draw focus rect}
    FCurrentPos: TPoint;
    FChildren: TInterfaceList;
    function GetFocus: boolean;
    procedure SetTheFocus(const Value: boolean);
  protected                     {used to draw focus rect}
    procedure Paint; override;  {used to draw focus rect}
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetFirstComponent: ICPRSDialogComponent;
    function GetNextComponent: ICPRSDialogComponent;
    property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
    property Focus:  boolean read GetFocus write SetTheFocus; {to draw focus rect}
    property OnKeyPress;        {to click the checkbox when spacebar is pressed}
  end;

  TVitalComboBox = class;

  TVitalEdit = class(TEdit, ICPRSDialogComponent)
  private
    FLinkedCombo: TVitalComboBox;
    FCPRSDialogInfo: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
    property LinkedCombo: TVitalComboBox read FLinkedCombo write FLinkedCombo;
  end;

  TVitalComboBox = class(TComboBox, ICPRSDialogComponent)
  private
    FLinkedEdit: TVitalEdit;
    FCPRSDialogInfo: ICPRSDialogComponent;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SelectByID(Value: string);
    property LinkedEdit: TVitalEdit read FLinkedEdit write FLinkedEdit;
    property CPRSDialogData: ICPRSDialogComponent read FCPRSDialogInfo implements ICPRSDialogComponent;
  end;

  TMentalHealthMemo = class(TMemo);

procedure ScreenReaderSystem_CurrentCheckBox(CheckBox: TCPRSDialogParentCheckBox);
procedure ScreenReaderSystem_CurrentLabel(lbl: TCPRSDialogStaticLabel);
procedure ScreenReaderSystem_CurrentComponent(component: ICPRSDialogComponent);
procedure ScreenReaderSystem_AddText(text: string);
procedure ScreenReaderSystem_Continue;
procedure ScreenReaderSystem_Stop;
procedure ScreenReaderSystem_Clear;
//function ScreenReaderSystem_GetPendingText: string;

const
// Screen Reader will stop reading in a TDlgFieldPanel at all classes except these:
  ReminderScreenReaderReadThroughClasses: array[1..3] of TClass =
    (TUpDown, TLabel, TVitalComboBox);

implementation

uses System.UITypes, System.Types, uCore, ORClasses, ORFn, VA508AccessibilityRouter, uTemplateFields;

var
  SRCheckBox: TCPRSDialogParentCheckBox = nil;
  SRLabel: TCPRSDialogStaticLabel = nil;
  SRComp: ICPRSDialogComponent = nil;
  SRText: string = '';
  SRContinuePending: boolean = FALSE;

procedure UpdatePending;
begin
  if SRContinuePending then
  begin
    if assigned(SRComp) then
    begin
      SRComp.AfterText := SRText;
      SRText := '';
      if assigned(SRLabel) then
      begin
        SRLabel.TabStop := FALSE;
        SRLabel := nil;
      end;
    end;
    SRComp := nil;
    SRContinuePending := FALSE;
  end;
end;

procedure UpdateCheckBox;
begin
  if assigned(SRCheckBox) then
  begin
    SRCheckBox.AccessText := SRText;
    SRText := '';
    if assigned(SRLabel) then
      SRLabel.TabStop := false;
    SRLabel := nil;
    SRCheckBox := nil;
  end;
end;

procedure ScreenReaderSystem_CurrentCheckBox(CheckBox: TCPRSDialogParentCheckBox);
begin
  ScreenReaderSystem_Stop;
  SRCheckBox := CheckBox;
end;

procedure ScreenReaderSystem_CurrentLabel(lbl: TCPRSDialogStaticLabel);
begin
  if assigned(SRLabel) then
    ScreenReaderSystem_Stop;
  SRLabel := lbl;
end;

procedure ScreenReaderSystem_CurrentComponent(component: ICPRSDialogComponent);
begin
  UpdateCheckBox;
  UpdatePending;
  if component.RequiredField then
  begin
    if SRText <> '' then
      SRText := SRText + ' ';
    SRText := SRText + 'required field';
  end;
  if (SRText <> '') and (component.Component is TCPRSDialogCheckBox) then
    ScreenReaderSystem_Stop;
  SRComp := component;
  if SRText = '' then
    SRComp.BeforeText := ' '
  else
    SRComp.BeforeText := SRText;
  SRText := '';
  if assigned(SRLabel) then
  begin
    SRLabel.TabStop := FALSE;
    SRLabel := nil;
  end;
end;

procedure ScreenReaderSystem_AddText(text: string);
begin
  if RightStr(Text,1) = '*' then
    delete(text, length(text),1);
  if Text <> '' then
  begin
    if SRText <> '' then
      SRText := SRText + ' ';
    SRText := SRText + text;
  end;
end;

procedure ScreenReaderSystem_Continue;
begin
  if assigned(SRComp) then
    SRContinuePending := TRUE
  else
    SRContinuePending := FALSE;
end;

procedure ScreenReaderSystem_Stop;
begin
  UpdateCheckBox;
  UpdatePending;
  ScreenReaderSystem_Clear;
end;

procedure ScreenReaderSystem_Clear;
begin
  SRCheckBox := nil;
  if assigned(SRLabel) and (trim(SRLabel.Caption) = '') then
    SRLabel.TabStop := false;
  SRLabel := nil;
  SRComp := nil;
  SRText := '';
  SRContinuePending := FALSE;
end;

function ScreenReaderSystem_GetPendingText: string;
begin
  Result := SRText;
  SRText := '';
end;

function GetDialogControlText(Control: TControl): string;
var
  Len: Integer;
begin
  if Control is TButton then
    Result := TButton(Control).Caption
  else
  begin
    Len := Control.GetTextLen;
    SetString(Result, PChar(nil), Len);
    if Len <> 0 then Control.GetTextBuf(Pointer(Result), Len + 1);
  end;
end;

constructor TDlgFieldPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FChildren := TInterfaceList.Create;
end;

destructor TDlgFieldPanel.Destroy;
begin
  if(assigned(FOnDestroy)) then
    FOnDestroy(Self);
  FreeAndNil(FChildren);
  inherited;
end;

function TDlgFieldPanel.GetFirstComponent: ICPRSDialogComponent;
begin
  FCurrentPos := Point(-1,-1);
  Result := GetNextComponent;
end;

function TDlgFieldPanel.GetFocus: boolean;
begin
  result := Focused;
end;


function TDlgFieldPanel.GetNextComponent: ICPRSDialogComponent;
var
  Comp: ICPRSDialogComponent;
  Control: TControl;
  i: integer;
  MinLeft, MinTop, MinXGap, MinYGap, gap: Integer;
  ok: boolean;

begin
  MinLeft := FCurrentPos.x;
  MinTop := FCurrentPos.Y;
  MinXGap := MaxInt;
  MinYGap := MaxInt;
  Result := nil;
  for I := 0 to FChildren.Count - 1 do
  begin
    Comp := ICPRSDialogComponent(FChildren[i]);
    try
      Control := Comp.Component;
      if assigned(Control) then
      begin
        ok := (Control.Top > MinTop);
        if (not ok) and (Control.Top = MinTop) and (Control.Left > MinLeft) then
          ok := TRUE;
        if ok then
        begin
          ok := FALSE;
          gap := Control.Top - MinTop;
          if gap < MinYGap then
          begin
            MinYGap := gap;
            MinXGap := Control.Left;
            ok := true;
          end
          else
          if (MinYGap = gap) and (Control.Left < MinXGap) then
          begin
            MinXGap := Control.Left;
            ok := TRUE;
          end;
          if ok then
          begin
            Result := Comp;
          end;
        end;
      end;
    finally
      Comp := nil;
    end;
  end;
  if assigned(Result) then
  begin
    FCurrentPos.x := Result.Component.Left;
    FCurrentPos.Y := Result.Component.Top;
  end;
end;
  
procedure TDlgFieldPanel.Paint;
var
  DC: HDC;
  R: TRect;

begin
  inherited;
  if(Focused) then
  begin
    if(not assigned(FCanvas)) then
      FCanvas := TControlCanvas.Create;
    DC := GetWindowDC(Handle);
    try
      FCanvas.Handle := DC;
      R := ClientRect;
      InflateRect(R, -1, -1);
      FCanvas.DrawFocusRect(R);
    finally
      ReleaseDC(Handle, DC);
    end;
  end;
end;

procedure TDlgFieldPanel.SetTheFocus(const Value: boolean);
begin
  if Value then
    SetFocus;
end;

{ TVitalComboBox }

constructor TVitalComboBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Edit Combo');
end;

destructor TVitalComboBox.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

procedure TVitalComboBox.SelectByID(Value: string);
var
  i: integer;

begin
  for i := 0 to Items.Count-1 do
    if(Value = Items[i]) then
    begin
      ItemIndex := i;
      break;
    end;
end;

{ TCPRSTemplateFieldEdit }

constructor TCPRSDialogFieldEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogData := TCPRSDialogComponent.Create(Self, 'Edit');
end;

destructor TCPRSDialogFieldEdit.Destroy;
begin
  FCPRSDialogData := nil;
  inherited;
end;

{ TCPRSTemplateFieldComboBox }

constructor TCPRSDialogComboBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogData := TCPRSDialogComponent.Create(Self, 'Edit Combo');
end;

destructor TCPRSDialogComboBox.Destroy;
begin
  FCPRSDialogData := nil;
  inherited;
end;

{ TCPRSTemplateFieldCheckBox }

constructor TCPRSDialogCheckBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogData := TCPRSDialogComponent.Create(Self, 'Check Box');
end;

destructor TCPRSDialogCheckBox.Destroy;
begin
  FCPRSDialogData := nil;
  inherited;
end;

{ TCPRSDialogButton }

constructor TCPRSDialogButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogData := TCPRSDialogComponent.Create(Self, 'Button');
end;

destructor TCPRSDialogButton.Destroy;
begin
  FCPRSDialogData := nil;
  inherited;
end;

{ TCPRSTemplateFieldDateCombo }

constructor TCPRSDialogDateCombo.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ORYearEditClass :=  TCPRSDialogYearEdit;
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Date Fields','Year');
end;

destructor TCPRSDialogDateCombo.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

{ TCPRSTemplateFieldDateBox }

constructor TCPRSDialogDateBox.Create(AOwner: TComponent);
//var
//  i: integer;
//  btn: TORDateButton;

begin
  inherited Create(AOwner);
{  for i := 0 to ControlCount - 1 do
  begin
    if Controls[i] is TORDateButton then
    begin
      btn := TORDateButton(Controls[i]);
      if ScreenReaderSystemActive then
        btn.TabStop := TRUE;
      break;
    end;
  end;}
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Date Edit', 'Date');
end;

destructor TCPRSDialogDateBox.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

{ TCPRSTemplateFieldNumber }

constructor TCPRSDialogNumber.CreatePanel(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FEdit := TCPRSNumberField.Create(Self);
  FEdit.Parent := Self;
  FEdit.BorderStyle := bsNone;
  FEdit.Top := 0;
  FEdit.Left := 0;
  FEdit.AutoSelect := True;
  FUpDown := TUpDown.Create(Self);
  FUpDown.Parent := Self;
  FUpDown.Associate := FEdit;
  FUpDown.Thousands := FALSE;
  FEdit.Tag := Integer(FUpDown);
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Numeric Edit', 'Numeric');
end;

destructor TCPRSDialogNumber.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

{ TCPRSTemplateWebLabel }

procedure TCPRSDialogHyperlinkLabel.Clicked(Sender: TObject);
begin
  GotoWebPage(FURL);
end;

type
  TFontFriend = class(TFont);

constructor TCPRSDialogHyperlinkLabel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Hyper Link', 'Hyper Link');
  OnKeyPress := KeyPressed;
end;

destructor TCPRSDialogHyperlinkLabel.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

procedure TCPRSDialogHyperlinkLabel.Init(Addr: string);
begin
  FURL := Addr;
  OnClick := Clicked;
  StaticLabel.OnClick := Clicked;
  Font.Assign(TORExposedControl(Parent).Font);
  Font.Color := Get508CompliantColor(clBlue);
  Font.Style := Font.Style + [fsUnderline];
  TFontFriend(Font).Changed; //  AdjustBounds; // make sure we have the right width
  AutoSize := FALSE;
  Height := Height + 1; // Courier New doesn't support underline unless it's higher
  Cursor := crHandPoint;
end;

procedure TCPRSDialogHyperlinkLabel.KeyPressed(Sender: TObject;
  var Key: Char);
begin
  if ord(Key) = VK_SPACE then
    Clicked(Self);  
end;

{ TCPRSTemplateFieldRichEdit }

constructor TCPRSDialogRichEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogData := TCPRSDialogComponent.Create(Self, 'Edit');
end;

destructor TCPRSDialogRichEdit.Destroy;
begin
  FCPRSDialogData := nil;
  inherited;
end;

{ TCPRSDialogData }

function TCPRSDialogComponent.Component: TControl;
begin
  Result := FComponent;
end;

constructor TCPRSDialogComponent.Create(AComponent: TControl; AComponentName: string;
        FollowOnCaption: string = '');
begin
  inherited Create;
  FComponent := AComponent;
  FSRBreak := TRUE;
  FComponentName := AComponentName;
  FFollowOnCaption := FollowOnCaption;
  FRequiredField := FALSE;
end;

function TCPRSDialogComponent.AccessText: string;
begin
  if FAfterText = '' then
    Result := FBeforeText
  else if FBeforeText = '' then
  begin
    if FAfterText <> '' then    
      Result := FComponentName + ', ' + FAfterText + ','
    else
      Result := '';
  end
  else
    Result := FBeforeText + ', ' + FComponentName + ', ' + FAfterText + ',';
  if FFollowOnCaption <> '' then
  begin
    if Result <> '' then
      Result := Result + ' ';
    Result := Result + ' ' + FFollowOnCaption;
  end;
end;

function TCPRSDialogComponent.GetAfterText: string;
begin
  Result := FAfterText;
end;

function TCPRSDialogComponent.GetBeforeText: string;
begin
  Result := FBeforeText;
end;

function TCPRSDialogComponent.GetRequiredField: boolean;
begin
  Result := FRequiredField;
end;

function TCPRSDialogComponent.GetSRBreak: boolean;
begin
  Result := FSRBreak;
end;

procedure TCPRSDialogComponent.SetAfterText(value: string);
begin
  FAfterText := Value;
end;

procedure TCPRSDialogComponent.SetBeforeText(Value: string);
begin
  FBeforeText := Value;
end;

procedure TCPRSDialogComponent.SetRequiredField(Value: boolean);
begin
  FRequiredField := Value;
end;

procedure TCPRSDialogComponent.SetSRBreak(Value: boolean);
begin
  FSRBreak := Value;
end;

{ TVitalEdit }

constructor TVitalEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCPRSDialogInfo := TCPRSDialogComponent.Create(Self, 'Vital Edit','Vital');
end;

destructor TVitalEdit.Destroy;
begin
  FCPRSDialogInfo := nil;
  inherited;
end;

initialization

finalization
  ScreenReaderSystem_Clear;

end.
