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.