VistA-cprs/CPRS-Chart/fMHTest.pas

884 lines
21 KiB
Plaintext

unit fMHTest;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ORCtrls, ORFn, uConst;
type
TfrmMHTest = class(TForm)
sbMain: TScrollBox;
pnlBottom: TPanel;
btnCancel: TButton;
btnOK: TButton;
btnClear: TButton;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure sbMainResize(Sender: TObject);
procedure btnOKClick(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure btnClearClick(Sender: TObject);
private
FIDCount: integer;
FAnswers: TStringList;
FObjs: TList;
FInfoText: string;
FInfoLabel: TMemo;
FBuilt: boolean;
FMaxLines: integer;
FBuildingControls: boolean;
procedure BuildControls;
function Answers: string;
procedure GetQText(QText: TStringList);
function LoadTest(InitialAnswers, TestName: string): boolean;
function CurrentQ: integer;
procedure GotoQ(x: integer);
public
MHTestComp: string;
MHA3: boolean;
end;
function PerformMHTest(InitialAnswers, TestName: string; QText: TStringList): string;
implementation
uses rReminders;
{$R *.DFM}
const
MaxQ = 100; // Max # of allowed answers for one question
LineNumberTag = 1;
ComboBoxTag = 2;
BevelTag = 3;
QuestionLabelTag = 4;
CheckBoxTag = 10;
NumberThreshhold = 5; // min # of questions on test before each has a line number
Skipped = 'X';
QGap = 4;
Gap = 2;
var
frmMHTest: TfrmMHTest;
FFirstCtrl: TList;
FYPos: TList;
type
TMHQuestion = class(TObject)
private
FSeeAnswers: boolean;
FAnswerText: string;
FText: string;
FAllowedAnswers: string;
FAnswerIndex: integer;
FAnswerCount: integer;
FID: integer;
FAnswer: string;
FObjects: TList;
FLine: integer;
protected
procedure OnChange(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
function Question: string;
procedure BuildControls(var Y: integer; Wide: integer);
property AllowedAnswers: string read FAllowedAnswers;
property Answer: string read FAnswer;
property AnswerCount: integer read FAnswerCount;
property AnswerIndex: integer read FAnswerIndex;
property AnswerText: string read FAnswerText;
property SeeAnswers: boolean read FSeeAnswers;
property ID: integer read FID;
property Text: string read FText;
end;
procedure ProcessMsg;
var
SaveCursor: TCursor;
begin
if(Screen.Cursor = crHourGlass) then
begin
SaveCursor := Screen.Cursor;
Screen.Cursor := crDefault;
try
Application.ProcessMessages;
finally
Screen.Cursor := SaveCursor;
end;
end
else
Application.ProcessMessages;
end;
function PerformMHTest(InitialAnswers, TestName: string; QText: TStringList): string;
begin
Result := InitialAnswers;
frmMHTest := TfrmMHTest.Create(Application);
try
frmMHTest.Caption := TestName;
if(frmMHTest.LoadTest(InitialAnswers, TestName)) then
begin
if(frmMHTest.ShowModal = mrOK) then
begin
Result := frmMHTest.Answers;
if(assigned(QText)) then
begin
QText.Clear;
if(Result <> '') then
frmMHTest.GetQText(QText);
end;
end;
end;
if frmMHTest.MHTestComp = '' then frmMHTest.MHTestComp := '0';
Result := Result + U + frmMHTest.MHTestComp;
if Result = U then Result := '';
finally
frmMHTest.Free;
end;
end;
{ TfrmMHTest }
function TfrmMHTest.Answers: string;
var
i, XCnt: integer;
ans: string;
begin
Result := '';
XCnt := 0;
for i := 0 to FObjs.Count-1 do
begin
ans := TMHQuestion(FObjs[i]).FAnswer;
if(ans = Skipped) then
inc(XCnt);
Result := Result + ans;
end;
if(XCnt = FObjs.Count) then
Result := '';
end;
function TfrmMHTest.LoadTest(InitialAnswers, TestName: string): boolean;
var
TstData: TStringList;
lNum, i, idx: integer;
Line, LastLine, Inp, Code: string;
Txt, Spec, p, Spidx, tmp: string;
RSpec, First, TCodes: boolean;
QObj: TMHQuestion;
procedure ParseText;
var
i, tlen: integer;
begin
Code := '';
i := 1;
tlen := length(Txt);
while(i <= tlen) do
begin
while(i <= tlen) and (Txt[i] = ' ') do inc(i);
if(i > tlen) then
begin
Txt := '';
exit;
end;
if(i > 1) then
begin
delete(Txt,1,i-1);
i := 1;
end;
if(Spec = 'I') then exit;
tlen := length(Txt);
if(tlen < 3) then exit;
Code := copy(Txt,i,1);
if(pos(Code, (UpperCaseLetters + LowerCaseLetters + Digits)) = 0) then
begin
Code := '';
exit;
end;
inc(i);
while(i <= tlen) and (Txt[i] = ' ') do inc(i);
if(Txt[i] in ['.','=']) then
begin
if(pos(Code, QObj.FAllowedAnswers) > 0) then
begin
inc(i);
while(i <= tlen) and (Txt[i] = ' ') do inc(i);
if(i <= tlen) then
delete(Txt,1,i-1)
else
Code := '';
exit;
end
else
begin
Code := '';
exit;
end;
end
else
begin
Code := '';
exit;
end;
end;
end;
procedure AddTxt2Str(var X: string);
begin
if(Txt <> '') then
begin
if(X <> '') then
begin
X := X + ' ';
if(copy(Txt, length(Txt), 1) = '.') then
X := X + ' ';
end;
X := X + Txt;
end;
end;
begin
Result := TRUE;
TstData := TStringList.Create;
try
TstData.Assign(LoadMentalHealthTest(TestName));
if TstData.Strings[0] = '1' then MHA3 := True
else MHA3 := False;
Screen.Cursor := crHourGlass;
try
TstData.Add('99999;X;0');
idx := 1;
FMaxLines := 0;
FInfoText := '';
LastLine := U;
First := TRUE;
RSpec := FALSE;
TCodes := FALSE;
QObj := nil;
while (idx < TstData.Count) do
begin
Inp := TstData[idx];
if(pos('[ERROR]', Inp) > 0) then
begin
Result := FALSE;
break;
end;
p := Piece(Inp, U, 1);
Line := Piece(p, ';', 1);
Spec := Piece(p, ';', 2);
SpIdx := Piece(p, ';', 3);
if(LastLine <> Line) then
begin
LastLine := Line;
if(First) then
First := FALSE
else
begin
if(not RSpec) then
begin
Result := FALSE;
break;
end;
end;
if(Spec = 'X') then break;
lNum := StrToIntDef(Line, 0);
if(lNum <= 0) then
begin
Result := FALSE;
break;
end;
RSpec := FALSE;
TCodes := FALSE;
QObj := TMHQuestion(FObjs[FObjs.Add(TMHQuestion.Create)]);
QObj.FLine := lNum;
if(FMaxLines < lNum) then
FMaxLines := lNum;
end;
Txt := Piece(Inp, U, 2);
ParseText;
if(Txt <> '') then
begin
if(Spec = 'I') then
begin
if MHA3 = True then AddTxt2Str(QObj.FText)
else
AddTxt2Str(FInfoText);;
end
else
if(Spec = 'R') then
begin
RSpec := TRUE;
if(spIdx = '0') then
QObj.FAllowedAnswers := Txt
else
if(Code = '') then
QObj.FAnswerText := Txt
else
begin
QObj.FSeeAnswers := FALSE;
FAnswers.Add(Code + U + Txt);
inc(QObj.FAnswerCount);
end;
end
else
if(Spec = 'T') then
begin
if(Code = '') then
begin
if(TCodes) then
begin
tmp := FAnswers[FAnswers.Count-1];
AddTxt2Str(tmp);
FAnswers[FAnswers.Count-1] := tmp;
end
else
AddTxt2Str(QObj.FText);
end
else
begin
TCodes := TRUE;
FAnswers.Add(Code + U + Txt);
inc(QObj.FAnswerCount);
end;
end;
end;
inc(idx);
end;
finally
Screen.Cursor := crDefault;
end;
finally
TstData.Free;
end;
if(not Result) then
ShowMessage('Error encountered loading ' + TestName)
else
begin
for i := 0 to FObjs.Count-1 do
begin
with TMHQuestion(FObjs[i]) do
begin
tmp := copy(InitialAnswers,i+1,1);
if(tmp <> '') then
FAnswer := tmp;
end;
end;
end;
end;
procedure TfrmMHTest.FormCreate(Sender: TObject);
begin
ResizeAnchoredFormToFont(self);
FAnswers := TStringList.Create;
FObjs := TList.Create;
FFirstCtrl := TList.Create;
FYPos := TList.Create;
end;
procedure TfrmMHTest.FormDestroy(Sender: TObject);
begin
KillObj(@FFirstCtrl);
KillObj(@FYPos);
KillObj(@FObjs, TRUE);
KillObj(@FAnswers);
end;
procedure TfrmMHTest.BuildControls;
var
i, Wide, Y: integer;
BoundsRect: TRect;
begin
if(not FBuildingControls) then
begin
FBuildingControls := TRUE;
try
Wide := sbMain.Width - (Gap * 2) - ScrollBarWidth - 4;
Y := gap - sbMain.VertScrollBar.Position;
if MHA3 = False then
begin
if(not assigned(FInfoLabel)) then
begin
FInfoLabel := TMemo.Create(Self);
FInfoLabel.Color := clBtnFace;
FInfoLabel.BorderStyle := bsNone;
FInfoLabel.ReadOnly := TRUE;
FInfoLabel.TabStop := FALSE;
FInfoLabel.Parent := sbMain;
FInfoLabel.WordWrap := TRUE;
FInfoLabel.Text := FInfoText;
FInfoLabel.Left := Gap;
end;
BoundsRect := FInfoLabel.BoundsRect;
//Wide := sbMain.Width - (Gap * 2) - ScrollBarWidth - 4;
//Y := gap - sbMain.VertScrollBar.Position;
BoundsRect.Top := Y;
BoundsRect.Right := BoundsRect.Left + Wide;
WrappedTextHeightByFont(Canvas, FInfoLabel.Font, FInfoLabel.Text, BoundsRect);
BoundsRect.Right := BoundsRect.Left + Wide;
FInfoLabel.BoundsRect := BoundsRect;
ProcessMsg;
inc(Y, FInfoLabel.Height + QGap);
for i := 0 to FObjs.Count-1 do
TMHQuestion(FObjs[i]).BuildControls(Y, Wide);
end
else
begin
inc(Y, 1);
for i := 0 to FObjs.Count-1 do TMHQuestion(FObjs[i]).BuildControls(Y, Wide);
end;
finally
FBuildingControls := FALSE;
end;
end;
end;
procedure TfrmMHTest.GetQText(QText: TStringList);
var
i, lx: integer;
begin
if(FObjs.Count > 99) then
lx := 5
else
if(FObjs.Count > 9) then
lx := 4
else
lx := 3;
for i := 0 to FObjs.Count-1 do
QText.Add(copy(IntToStr(i+1) + '. ', 1, lx) + TMHQuestion(FObjs[i]).Question);
end;
function TfrmMHTest.CurrentQ: integer;
var
i, j: integer;
ctrl: TWinControl;
MHQ: TMHQuestion;
begin
Result := 0;
ctrl := ActiveControl;
if(not assigned(Ctrl)) then
exit;
for i := 0 to FObjs.Count-1 do
begin
MHQ := TMHQuestion(FObjs[i]);
for j := 0 to MHQ.FObjects.Count-1 do
begin
if(Ctrl = MHQ.FObjects[j]) then
begin
Result := i;
exit;
end;
end;
end;
end;
procedure TfrmMHTest.GotoQ(x: integer);
begin
if(ModalResult <> mrNone) then exit;
if(x < 0) then x := 0;
if(x >= FYPos.Count) then
begin
btnOK.Default := TRUE;
btnOK.SetFocus;
end
else
begin
btnOK.Default := FALSE;
sbMain.VertScrollBar.Position := Integer(FYPos[x]) - 2;
TWinControl(FFirstCtrl[x]).SetFocus;
end;
end;
procedure TfrmMHTest.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_PRIOR then
begin
GotoQ(CurrentQ - 1);
Key := 0;
end
else
if (Key = VK_NEXT) or (Key = VK_RETURN) then
begin
GotoQ(CurrentQ + 1);
Key := 0;
end;
end;
{ TMHQuestion }
procedure TMHQuestion.BuildControls(var Y: integer; Wide: integer);
var
RCombo: TComboBox;
LNLbl, RLbl: TMemo;
Bvl: TBevel;
cb: TORCheckBox;
ans, idx, DX, MaxDX, MaxDY: integer;
Offset: integer;
txt: string;
QNum: integer;
function GetCtrl(SubTag: integer): TControl;
var
i: integer;
begin
Result := nil;
for i := 0 to FObjects.Count-1 do
begin
if(TControl(FObjects[i]).Tag = (FID + SubTag)) then
begin
Result := TControl(FObjects[i]);
break;
end;
end;
end;
procedure AdjDY(Ht: integer);
begin
if(MaxDY < Ht) then
MaxDY := Ht;
end;
procedure GetRLbl;
var
BoundsRect: TRect;
begin
if(FText <> '') then
begin
RLbl := TMemo(GetCtrl(QuestionLabelTag));
if(not assigned(RLbl)) then
begin
RLbl := TMemo.Create(frmMHTest);
RLbl.Color := clBtnFace;
RLbl.BorderStyle := bsNone;
RLbl.ReadOnly := TRUE;
RLbl.TabStop := FALSE;
RLbl.Parent := frmMHTest.sbMain;
RLbl.Tag := FID + QuestionLabelTag;
RLbl.WordWrap := TRUE;
RLbl.Text := FText;
FObjects.Add(RLbl);
end;
BoundsRect.Top := Y;
BoundsRect.Left := Offset;
BoundsRect.Right := Wide;
WrappedTextHeightByFont(frmMHTest.Canvas, RLbl.Font, RLbl.Text, BoundsRect);
BoundsRect.Right := Wide;
RLbl.BoundsRect := BoundsRect;
ProcessMsg;
end
else
RLbl := nil;
end;
begin
QNum := (FID div MaxQ)-1;
while(FFirstCtrl.Count <= QNum) do
FFirstCtrl.Add(nil);
while(FYPos.Count <= QNum) do
FYPos.Add(nil);
FYPos[QNum] := Pointer(Y);
ans := pos(FAnswer, FAllowedAnswers) - 1;
Offset := Gap;
if(not assigned(FObjects)) then
FObjects := TList.Create;
MaxDY := 0;
if(frmMHTest.FObjs.Count >= NumberThreshhold) then
begin
LNLbl := TMemo(GetCtrl(LineNumberTag));
if(not assigned(LNLbl)) then
begin
LNLbl := TMemo.Create(frmMHTest);
LNLbl.Color := clBtnFace;
LNLbl.BorderStyle := bsNone;
LNLbl.ReadOnly := TRUE;
LNLbl.TabStop := FALSE;
LNLbl.Parent := frmMHTest.sbMain;
LNLbl.Tag := FID + LineNumberTag;
LNLbl.Text := IntToStr(QNum+1) + '.';
LNLbl.Width := TextWidthByFont(LNLbl.Font.Handle, LNLbl.Text);
LNLbl.Height := TextHeightByFont(LNLbl.Font.Handle, LNLbl.Text);
FObjects.Add(LNLbl);
end;
LNLbl.Top := Y;
LNLbl.Left := Offset;
inc(Offset, MainFontSize * 4);
AdjDY(LNLbl.Height);
end;
Bvl := TBevel(GetCtrl(BevelTag));
if(not assigned(Bvl)) then
begin
Bvl := TBevel.Create(frmMHTest);
Bvl.Parent := frmMHTest.sbMain;
Bvl.Tag := FID + BevelTag;
Bvl.Shape := bsFrame;
FObjects.Add(Bvl);
end;
Bvl.Top := Y;
Bvl.Left := Offset;
Bvl.Width := Wide - Offset;
inc(Offset, Gap * 2);
inc(Y, Gap * 2);
dec(Wide, Offset + (Gap * 2));
GetRLbl;
if(assigned(RLbl)) then
begin
MaxDY := RLbl.Height;
inc(Y, MaxDY + Gap * 2);
end;
if(FSeeAnswers) then
begin
for idx := 0 to FAnswerCount-1 do
begin
cb := TORCheckBox(GetCtrl(CheckBoxTag + idx));
if(not assigned(cb)) then
begin
cb := TORCheckBox.Create(frmMHTest);
if(idx = 0) then
FFirstCtrl[QNum] := cb;
cb.Parent := frmMHTest.sbMain;
cb.Tag := FID + CheckBoxTag + idx;
cb.GroupIndex := FID;
cb.WordWrap := TRUE;
cb.AutoSize := TRUE;
if(idx = ans) then
cb.Checked := TRUE;
cb.OnClick := OnChange;
cb.Caption := Piece(frmMHTest.FAnswers[FAnswerIndex + idx], U, 2);
FObjects.Add(cb);
end;
cb.Top := Y;
cb.Left := Offset;
cb.WordWrap := TRUE;
cb.Width := Wide;
cb.AutoAdjustSize;
cb.WordWrap := (not cb.SingleLine);
inc(Y, cb.Height + Gap);
end;
end
else
begin
RCombo := TComboBox(GetCtrl(ComboBoxTag));
if(not assigned(RCombo)) then
begin
RCombo := TComboBox.Create(frmMHTest);
FFirstCtrl[QNum] := RCombo;
RCombo.Parent := frmMHTest.sbMain;
RCombo.Tag := FID + ComboBoxTag;
FObjects.Add(RCombo);
MaxDX := 0;
for idx := 0 to FAnswerCount-1 do
begin
txt := Piece(frmMHTest.FAnswers[FAnswerIndex + idx], U, 2);
RCombo.Items.Add(txt);
DX := TextWidthByFont(frmMHTest.sbMain.Font.Handle, txt);
if(MaxDX < DX) then
MaxDX := DX;
end;
RCombo.ItemIndex := ans;
RCombo.Width := MaxDX + 24;
RCombo.OnChange := OnChange;
end;
RCombo.Top := Y;
RCombo.Left := Offset;
inc(Y, RCombo.Height + (Gap * 2));
end;
Bvl.Height := Y - Bvl.Top;
inc(Y, QGap);
end;
constructor TMHQuestion.Create;
begin
inherited;
FSeeAnswers := TRUE;
FAnswerText := '';
FText := '';
FAllowedAnswers := '';
FAnswerIndex := frmMHTest.FAnswers.Count;
FAnswerCount := 0;
inc(frmMHTest.FIDCount, MaxQ);
FID := frmMHTest.FIDCount;
FAnswer := Skipped;
end;
destructor TMHQuestion.Destroy;
begin
KillObj(@FObjects, TRUE);
inherited;
end;
procedure TMHQuestion.OnChange(Sender: TObject);
var
idx: integer;
cb: TCheckBox;
cbo: TComboBox;
begin
if(Sender is TCheckBox) then
begin
cb := TCheckBox(Sender);
if(cb.Checked) then
begin
idx := cb.Tag - CheckBoxTag + 1;
idx := idx mod MaxQ;
FAnswer := copy(FAllowedAnswers, idx, 1);
end
else
FAnswer := Skipped;
end
else
if(Sender is TComboBox) then
begin
cbo := TComboBox(Sender);
idx := cbo.ItemIndex + 1;
if(idx = 0) or (cbo.Text = '') then
FAnswer := Skipped
else
FAnswer := copy(FAllowedAnswers, idx, 1);
end;
end;
procedure TfrmMHTest.FormShow(Sender: TObject);
begin
if(not FBuilt) then
begin
Screen.Cursor := crHourGlass;
try
BuildControls;
FBuilt := TRUE;
finally
Screen.Cursor := crDefault;
end;
end;
end;
procedure TfrmMHTest.sbMainResize(Sender: TObject);
begin
if(FBuilt) then
BuildControls;
end;
function TMHQuestion.Question: string;
var
idx: integer;
echar: string;
begin
Result := trim(FText);
echar := copy(Result, length(Result), 1);
if(echar <> ':') and (echar <> '?') then
begin
if(echar = '.') then
delete(Result, length(result), 1);
Result := Result + ':';
end;
if(FAnswer = Skipped) then
Result := Result + ' Not rated'
else
begin
idx := pos(FAnswer, FAllowedAnswers) + FAnswerIndex - 1;
if(idx >= 0) and (idx < frmMHTest.FAnswers.Count) then
Result := Result + ' ' + Piece(frmMHTest.FAnswers[idx],U,2);
end;
end;
procedure TfrmMHTest.btnOKClick(Sender: TObject);
var
i, XCnt, First: integer;
msg, ans, TestStatus: string;
begin
msg := '';
ans := '';
XCnt := 0;
First := -1;
TestStatus := '2';
MHTestComp := '2';
for i := 0 to FObjs.Count-1 do
begin
ans := ans + TMHQuestion(Fobjs[i]).FAnswer;
if(TMHQuestion(FObjs[i]).FAnswer = Skipped) then
begin
if(First < 0) then First := i;
inc(XCnt);
if(msg <> '') then
msg := msg + ', ';
msg := msg + IntToStr(i+1);
end;
end;
if(XCnt = FObjs.Count) then ModalResult := mrOK;
TestStatus := VerifyMentalHealthTestComplete(Self.Caption, ans);
if Piece(TestStatus,U,1) <> '2' then
begin
if Piece(TestStatus,U,1)='1' then
begin
ModalResult := mrOK;
MHTestComp := '1';
EXIT;
end;
if Piece(TestStatus,U,1)='0' then
begin
MHTestComp := '0';
msg := Piece(TestStatus,u,2);
msg := 'The following questions have not been answered:' + CRLF + CRLF + ' ' + msg;
if(InfoBox(msg + CRLF + CRLF + 'Answer skipped questions?', 'Skipped Questions',
MB_YESNO or MB_ICONQUESTION) = IDYES) then GotoQ(First)
else
ModalResult := mrOK;
EXIT;
end;
end;
if(XCnt = 0) then
ModalResult := mrOK
else
begin
if(XCnt = FObjs.Count) then
ModalResult := mrOK
else
begin
msg := 'The following questions have not been answered:' + CRLF + CRLF + ' ' + msg;
if(InfoBox(msg + CRLF + CRLF + 'Answer skipped questions?', 'Skipped Questions',
MB_YESNO or MB_ICONQUESTION) = IDYES) then
GotoQ(First)
else
ModalResult := mrOK;
end;
end;
end;
procedure TfrmMHTest.btnClearClick(Sender: TObject);
var
i: integer;
begin
for i := 0 to sbMain.ControlCount-1 do
begin
if(sbMain.Controls[i] is TCheckBox) then
TCheckBox(sbMain.Controls[i]).Checked := FALSE
else
if(sbMain.Controls[i] is TComboBox) then
begin
with TComboBox(sbMain.Controls[i]) do
begin
ItemIndex := -1;
OnChange(sbMain.Controls[i]);
end;
end;
end;
end;
end.