VistA-cprs/VA/VA508Accessibility/VA508Classes.pas

562 lines
17 KiB
Plaintext

unit VA508Classes;
interface
uses SysUtils, Classes, Contnrs, StrUtils, Windows, HRParser, HRParserPas, Forms, Dialogs;
type
TFormData = class
private
FFileName: string;
FlcFormClassName: string;
FInheritedForm: boolean;
FParent: TFormData;
FManagerComponentName: string;
FInheritedManager: boolean;
FFormClassName: string;
FEmptyManager: boolean;
procedure SetFormClassName(const Value: string);
public
function HasManager: boolean;
function HasParent: boolean;
property FormClassName: string read FFormClassName write SetFormClassName;
property lcFormClassName: string read FlcFormClassName;
property EmptyManager: boolean read FEmptyManager write FEmptyManager;
property FileName: string read FFileName write FFileName;
property Parent: TFormData read FParent write FParent;
property InheritedForm: boolean read FInheritedForm write FInheritedForm;
property InheritedManager: boolean read FInheritedManager write FInheritedManager;
property ManagerComponentName: string read FManagerComponentName write FManagerComponentName;
end;
EVA508AccessibilityException = class(Exception);
TParentChildErrorCode = (pcNoParentManager, pcValidRelationship,
pcNoInheritence, pcNoChildComponent, pcEmptyManagerComponent,
pcOtherChildComponent, pcInheritedNoParent);
const
TParentChildPassCodes = [pcNoParentManager, pcValidRelationship];
TParentChildFailCodes = [pcNoInheritence, pcNoChildComponent, pcEmptyManagerComponent,
pcOtherChildComponent, pcInheritedNoParent];
TAutoFixFailCodes = [pcNoInheritence, pcEmptyManagerComponent, pcNoChildComponent, pcInheritedNoParent];
type
TParentChildFormTracker = class
private
FData: TObjectList;
function FindForm(AFormClassName: String): TFormData;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure AddForm(AFileName, AFormClassName, AManagerComponentName: string;
AEmptyManager: boolean; AInheritedForm, AInheritedManager: boolean);
procedure AddLink(ParentFormClassName, ChildFormClassName: string);
function FormCount: integer;
function GetFormData(index: integer): TFormData;
function ParentChildErrorStatus(index: integer): TParentChildErrorCode;
function ParentChildErrorDescription(index: integer): string;
end;
TUnitSection = (usUnknown, usInterface, usImplementation);
TTokenState = (tsNormal, tsPendingEqualChar, tsPendingClassSymbol, tsPendingParenChar,
tsPendingClassName, tsPendingEndOfClass);
TVA508Parser = class
private
FClassName: String;
FParentClass: String;
FPendingParentClass: string;
FParser: THRParserPas;
FToken: THRToken;
FLastLine: integer;
FLastPos: integer;
FTokenName: String;
FState: TTokenState;
FUnitSection: TUnitSection;
FDone: boolean;
FIsSymbol: boolean;
FIsChar: boolean;
procedure ParseToken;
public
function GetParentClassName(ClassName, FileName: String;
InStream: TStream; var OutStream: TStream): String;
function LastLineRead: integer;
function LastPosition: integer;
end;
procedure VA508ComponentCreationCheck(AComponent, AOwner: TComponent;
AllowDataModules: boolean; ManagerRequired: boolean);
procedure VA508ComponentDestructionCheck(AComponent: TComponent);
const
NO_OWNER_ERROR = 'Cannot create a %s component without an owner';
implementation
uses
VA508AccessibilityManager, VA508ImageListLabeler;
const
MANAGER_CLASS_REQUIRED = 'Cannot create a %s component without a ' + #13#10 +
'%s component on the same form';
OTHER_COMPONENTS_DELETED = 'Deleting this %s component also deletes all' + #13#10 +
'A %s components on this form';
OWNER_NOT_ALLOWED = 'You may not place a %s component on a %s';
OWNER_REQUIREMENTS = '%s component can only be added to a %s';
HAS_EXISTING_MANAGER_ERROR = '%s alread has a %s component';
function HasAnotherAccessibilityManager(Root, AComponent: TComponent): boolean;
var
i: integer;
comp: TComponent;
begin
Result := false;
for i := 0 to AComponent.ComponentCount-1 do
begin
comp := AComponent.Components[i];
if (comp <> Root) and (comp is TVA508AccessibilityManager) then
begin
Result := true;
exit;
end;
if HasAnotherAccessibilityManager(Root, AComponent.Components[i]) then
begin
Result := true;
exit;
end;
end;
end;
procedure VA508ComponentCreationCheck(AComponent, AOwner: TComponent;
AllowDataModules: boolean; ManagerRequired: boolean);
var
msg: string;
procedure EnsureManager;
var
i: integer;
error: boolean;
begin
if (csDesigning in AOwner.ComponentState) and (not (csLoading in AOwner.ComponentState)) then
begin
error := TRUE;
for i := 0 to AOwner.ComponentCount-1 do
begin
if AOwner.Components[i] is TVA508AccessibilityManager then
begin
error := FALSE;
break;
end;
end;
if error then
begin
raise EVA508AccessibilityException.CreateFmt(MANAGER_CLASS_REQUIRED,
[AComponent.ClassName, TVA508AccessibilityManager.ClassName]);
end;
end;
end;
begin
if not assigned(AOwner) then
raise EVA508AccessibilityException.CreateFmt(NO_OWNER_ERROR, [AComponent.ClassName]);
if (AOwner is TDataModule) then
begin
if AllowDataModules then
exit
else
raise EVA508AccessibilityException.CreateFmt(OWNER_NOT_ALLOWED, [AComponent.ClassName, TDataModule.ClassName]);
end;
if not (AOwner is TCustomForm) then
begin
msg := 'Form';
if AllowDataModules then
msg := msg + ' or a Data Module';
raise EVA508AccessibilityException.CreateFmt(OWNER_REQUIREMENTS, [AComponent.ClassName, msg]);
end;
if ManagerRequired then
EnsureManager
else
begin
if HasAnotherAccessibilityManager(AComponent, AOwner) then
raise EVA508AccessibilityException.Create(Format(HAS_EXISTING_MANAGER_ERROR,
[AOwner.ClassName, AComponent.ClassName]));
end;
end;
procedure VA508ComponentDestructionCheck(AComponent: TComponent);
var
i: integer;
list: TObjectList;
msg: string;
ComponentAccessFound, ImageListLabelerFound: boolean;
Owner: TComponent;
begin
if not assigned(AComponent) then exit;
Owner := AComponent.Owner;
if not assigned(Owner) then exit;
if HasAnotherAccessibilityManager(AComponent, Owner) then exit;
if (csDesigning in AComponent.ComponentState) and (not (csDestroying in Owner.ComponentState)) then
begin
list := TObjectList.Create;
try
ComponentAccessFound := FALSE;
ImageListLabelerFound := FALSE;
for I := 0 to Owner.ComponentCount-1 do
begin
if Owner.Components[i] is TVA508ComponentAccessibility then
begin
ComponentAccessFound := TRUE;
list.Add(Owner.Components[i]);
end
else
if Owner.Components[i] is TVA508ImageListLabeler then
begin
ImageListLabelerFound := TRUE;
list.Add(Owner.Components[i]);
end
end;
msg := '';
if ImageListLabelerFound then
msg := TVA508ImageListLabeler.ClassName;
if ComponentAccessFound then
begin
if msg <> '' then
msg := msg + ' and ';
msg := msg + TVA508ComponentAccessibility.ClassName;
end;
if msg <> '' then
begin
MessageDlg(Format(OTHER_COMPONENTS_DELETED, [AComponent.ClassName, msg]), mtWarning, [mbOK], 0);
end;
finally
list.Free;
end;
end;
end;
{ TFormData }
function TFormData.HasManager: boolean;
begin
Result := ManagerComponentName <> '';
end;
function TFormData.HasParent: boolean;
begin
Result := assigned(Parent);
end;
procedure TFormData.SetFormClassName(const Value: string);
begin
FFormClassName := Value;
FlcFormClassName := lowerCase(Value);
end;
{ TParentChildFormTracker }
procedure TParentChildFormTracker.AddForm(AFileName, AFormClassName, AManagerComponentName: string;
AEmptyManager: boolean; AInheritedForm, AInheritedManager: boolean);
var
data: TFormData;
begin
if FindForm(AFormClassName) = nil then
begin
Data := TFormData.Create;
data.FileName := AFileName;
data.FormClassName := AFormClassName;
data.ManagerComponentName := AManagerComponentName;
data.Parent := nil;
data.InheritedForm := AInheritedForm;
data.InheritedManager := AInheritedManager;
data.EmptyManager := AEmptyManager;
FData.Add(data);
end;
end;
procedure TParentChildFormTracker.AddLink(ParentFormClassName, ChildFormClassName: string);
var
child,parent: TFormData;
begin
child := FindForm(ChildFormClassName);
parent := FindForm(ParentFormClassName);
if assigned(child) and assigned(parent) then
child.Parent := parent;
end;
procedure TParentChildFormTracker.Clear;
begin
FData.Clear;
end;
constructor TParentChildFormTracker.Create;
begin
FData := TObjectList.Create;
end;
destructor TParentChildFormTracker.Destroy;
begin
FData.Free;
inherited;
end;
function TParentChildFormTracker.FindForm(AFormClassName: String): TFormData;
var
i: integer;
name: string;
begin
name := lowercase(AFormClassName);
Result := nil;
for i := 0 to FData.Count - 1 do
begin
if GetFormData(i).lcFormClassName = Name then
begin
Result := GetFormData(i);
exit;
end;
end;
end;
function TParentChildFormTracker.FormCount: integer;
begin
Result := FData.Count;
end;
function TParentChildFormTracker.GetFormData(index: integer): TFormData;
begin
Result := TFormData(FData[index]);
end;
function TParentChildFormTracker.ParentChildErrorDescription(index: integer): string;
var
code: TParentChildErrorCode;
parent: TFormData;
child: TFormData;
begin
code := ParentChildErrorStatus(index);
Result := '';
if code in [pcNoParentManager, pcValidRelationship] then exit;
child := GetFormData(index);
parent := child.Parent;
case code of
pcNoInheritence: Result := 'Form ' + child.FormClassName + ' descends from form ' + parent.FormClassName +
' but uses the word "object" instead of "inherited" in the .dfm file.';
pcNoChildComponent, pcEmptyManagerComponent: Result := 'Form ' + child.FormClassName +
' .dfm file needs to be rebuilt. To fix manually, view the form as text, then as a form, ' +
' make sure the form is in a modified state, and save it.';
pcOtherChildComponent: Result := 'Form ' + child.FormClassName + ' has two ' + TVA508AccessibilityManager.ClassName +
' components, one from an inherited form, and one on the form.' +
' Remove the component on the form and use the inherited component';
pcInheritedNoParent: Result := 'Form ' + child.FormClassName + ' has a ' + TVA508AccessibilityManager.ClassName +
' component, ' + child.ManagerComponentName +
', that was inherited from a parent form, but ' + child.ManagerComponentName +
' has been deleted from the parent form. To Remove the component, view the form as text, then as a form, ' +
' make sure the form is in a modified state, and save it. Or you can add the ' +
TVA508AccessibilityManager.ClassName + ' component back onto the parent form.';
else Result := '';
end;
end;
function TParentChildFormTracker.ParentChildErrorStatus(
index: integer): TParentChildErrorCode;
var
parent: TFormData;
child: TFormData;
bad: boolean;
begin
Result := pcNoParentManager;
child := GetFormData(index);
if not assigned(child) then exit;
bad := false;
if child.InheritedManager then
begin
bad := not child.HasParent;
if not bad then
bad := not child.InheritedForm;
if not bad then
bad := not child.Parent.HasManager;
end;
try
if not child.HasParent then exit;
parent := child.Parent;
if not parent.HasManager then exit;
if child.InheritedForm then
begin
if child.HasManager then
begin
if (parent.ManagerComponentName = child.ManagerComponentName) and
(child.InheritedManager) then
begin
if child.EmptyManager then
Result := pcEmptyManagerComponent
else
Result := pcValidRelationship
end
else
Result := pcOtherChildComponent
end
else
Result := pcNoChildComponent;
end
else
Result := pcNoInheritence;
finally
if bad and (Result = pcNoParentManager) then
Result := pcInheritedNoParent;
end;
end;
const
INTERFACE_NAME = 'interface';
IMPLEMENTATION_NAME = 'implementation';
CLASS_NAME = 'class';
LEFT_PAREN = '(';
RIGHT_PAREN = ')';
COMMA = ',';
EQUALS = '=';
{ TVA508Parser }
function TVA508Parser.GetParentClassName(ClassName, FileName: String;
InStream: TStream; var OutStream: TStream): String;
begin
FClassName := lowerCase(ClassName);
FParentClass := '';
FState := tsNormal;
FUnitSection := usUnknown;
FDone := false;
if(assigned(FParser)) then
FParser.Free;
FParser := THRParserPas.Create;
FLastLine := 0;
FLastPos := 0;
if assigned(InStream) then
FParser.Source := InStream
else
FParser.Source := TFileStream.Create(FileName, fmOpenRead, fmShareDenyNone);
try
while (not FDone) and (FParser.NextToken.TokenType <> HR_TOKEN_EOF) do
begin
FToken := FParser.Token;
FLastLine := FToken.Line;
FLastPos := FToken.SourcePos;
ParseToken;
end;
finally
if assigned(InStream) then
begin
InStream.Free;
OutStream := nil;
end
else
OutStream := FParser.Source;
FreeAndNil(FParser);
end;
Result := FParentClass;
end;
function TVA508Parser.LastLineRead: integer;
begin
Result := FLastLine;
end;
function TVA508Parser.LastPosition: integer;
begin
Result := FLastPos + 1;
end;
procedure TVA508Parser.ParseToken;
function IgnoreToken: boolean;
begin
if(FUnitSection = usImplementation) then
begin
Result := TRUE;
exit;
end;
case FToken.TokenType of
HR_TOKEN_TEXT_SPACE, HR_TOKEN_PAS_COMMENT_SLASH,
HR_TOKEN_PAS_COMMENT_BRACE_OPEN, HR_TOKEN_PAS_COMMENT_BRACE,
HR_TOKEN_PAS_COMMENT_BRACKET_OPEN, HR_TOKEN_PAS_COMMENT_BRACKET:
Result := TRUE;
else
Result := FALSE;
end;
end;
function InvalidSection: boolean;
var
changed: boolean;
begin
changed := false;
if FIsSymbol then
begin
if FTokenName = INTERFACE_NAME then
begin
FUnitSection := usInterface;
changed := true;
end
else if FTokenName = IMPLEMENTATION_NAME then
begin
FUnitSection := usImplementation;
FDone := TRUE;
changed := true;
end;
end;
Result := (FUnitSection <> usInterface);
if changed then
FState := tsNormal;
end;
begin
if(IgnoreToken) then exit;
FTokenName := LowerCase(FToken.Token);
FIsSymbol := (FToken.TokenType = HR_TOKEN_TEXT_SYMBOL);
FIsChar := (FToken.TokenType = HR_TOKEN_CHAR);
if(InvalidSection) then exit;
case FState of
tsNormal: if FIsSymbol and (FTokenName = FClassName) then
FState := tsPendingEqualChar;
tsPendingEqualChar: if FIsChar and (FTokenName = EQUALS) then
FState := tsPendingClassSymbol
else
FState := tsNormal;
tsPendingClassSymbol: if FIsSymbol and (FTokenName = CLASS_NAME) then
FState := tsPendingParenChar
else
FState := tsNormal;
tsPendingParenChar: if FIsChar and (FTokenName = LEFT_PAREN) then
FState := tsPendingClassName
else
FState := tsNormal;
tsPendingClassName: if FIsSymbol then
begin
FPendingParentClass := FToken.Token;
FState := tsPendingEndOfClass;
end
else
FState := tsNormal;
tsPendingEndOfClass: begin
if FIsChar and ((FTokenName = RIGHT_PAREN) or
(FTokenName = COMMA)) then
begin
FParentClass := FPendingParentClass;
FDone := TRUE;
end;
FState := tsNormal;
end;
else
FState := tsNormal;
end;
end;
end.