diff --git a/G940LEDControl/Forms/ButtonFunctionFrm.dfm b/G940LEDControl/Forms/ButtonFunctionFrm.dfm new file mode 100644 index 0000000..a9f3300 --- /dev/null +++ b/G940LEDControl/Forms/ButtonFunctionFrm.dfm @@ -0,0 +1,265 @@ +object ButtonFunctionForm: TButtonFunctionForm + Left = 0 + Top = 0 + ActiveControl = vstFunctions + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'Configure button' + ClientHeight = 484 + ClientWidth = 692 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poMainFormCenter + OnCreate = FormCreate + OnDestroy = FormDestroy + PixelsPerInch = 96 + TextHeight = 13 + object bvlHeader: TBevel + Left = 0 + Top = 50 + Width = 692 + Height = 2 + Align = alTop + Shape = bsTopLine + ExplicitTop = 41 + end + object pnlButtons: TPanel + AlignWithMargins = True + Left = 0 + Top = 441 + Width = 692 + Height = 43 + Margins.Left = 0 + Margins.Top = 8 + Margins.Right = 0 + Margins.Bottom = 0 + Align = alBottom + BevelOuter = bvNone + TabOrder = 3 + DesignSize = ( + 692 + 43) + object bvlFooter: TBevel + Left = 0 + Top = 0 + Width = 692 + Height = 8 + Align = alTop + Shape = bsTopLine + end + object btnOK: TButton + Left = 528 + Top = 10 + Width = 75 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'OK' + Default = True + TabOrder = 0 + OnClick = btnOKClick + end + object btnCancel: TButton + Left = 609 + Top = 10 + Width = 75 + Height = 25 + Anchors = [akTop, akRight] + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 1 + end + end + object vstFunctions: TVirtualStringTree + AlignWithMargins = True + Left = 8 + Top = 60 + Width = 257 + Height = 373 + Margins.Left = 8 + Margins.Top = 8 + Margins.Right = 0 + Margins.Bottom = 0 + Align = alLeft + Header.AutoSizeIndex = 0 + Header.Font.Charset = DEFAULT_CHARSET + Header.Font.Color = clWindowText + Header.Font.Height = -11 + Header.Font.Name = 'Tahoma' + Header.Font.Style = [] + Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible] + IncrementalSearch = isAll + TabOrder = 1 + TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScrollOnExpand, toAutoSort, toAutoTristateTracking, toAutoDeleteMovedNodes] + TreeOptions.MiscOptions = [toAcceptOLEDrop, toFullRepaintOnResize, toInitOnSave, toWheelPanning, toEditOnClick] + TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toThemeAware, toUseBlendedImages] + TreeOptions.SelectionOptions = [toFullRowSelect] + OnFocusChanged = vstFunctionsFocusChanged + OnGetText = vstFunctionsGetText + OnPaintText = vstFunctionsPaintText + OnIncrementalSearch = vstFunctionsIncrementalSearch + Columns = < + item + Position = 0 + Width = 253 + WideText = 'Available functions' + end> + end + object pnlFunction: TPanel + AlignWithMargins = True + Left = 273 + Top = 60 + Width = 411 + Height = 373 + Margins.Left = 8 + Margins.Top = 8 + Margins.Right = 8 + Margins.Bottom = 0 + Align = alClient + BevelOuter = bvNone + TabOrder = 2 + object pnlName: TPanel + Left = 0 + Top = 0 + Width = 411 + Height = 97 + Align = alTop + BevelOuter = bvNone + TabOrder = 0 + DesignSize = ( + 411 + 97) + object lblFunctionName: TLabel + Left = 0 + Top = 19 + Width = 405 + Height = 19 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = 'runtime: function' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 401 + end + object lblCategoryName: TLabel + Left = 0 + Top = 0 + Width = 405 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = 'runtime: category' + Font.Charset = DEFAULT_CHARSET + Font.Color = clGrayText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + ExplicitWidth = 401 + end + object lblHasStates: TLabel + Left = 0 + Top = 47 + Width = 401 + Height = 31 + AutoSize = False + Caption = + 'This function provides the following states. Each state can be c' + + 'ustomized by changing the color below.' + WordWrap = True + end + object lblNoStates: TLabel + Left = 0 + Top = 47 + Width = 195 + Height = 13 + Caption = 'This function has no configurable states.' + Visible = False + end + end + object sbStates: TScrollBox + Left = 0 + Top = 97 + Width = 411 + Height = 276 + Align = alClient + BorderStyle = bsNone + TabOrder = 1 + end + end + object pnlHeader: TPanel + Left = 0 + Top = 0 + Width = 692 + Height = 50 + Align = alTop + BevelOuter = bvNone + Color = clWindow + ParentBackground = False + TabOrder = 0 + DesignSize = ( + 692 + 50) + object lblButton: TLabel + Left = 8 + Top = 13 + Width = 24 + Height = 23 + Caption = 'P1' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -19 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object lblCurrentAssignment: TLabel + Left = 586 + Top = 8 + Width = 98 + Height = 13 + Alignment = taRightJustify + Anchors = [akTop, akRight] + Caption = 'Current assignment:' + end + object lblCurrentFunction: TLabel + Left = 587 + Top = 27 + Width = 97 + Height = 13 + Alignment = taRightJustify + Anchors = [akTop, akRight] + Caption = 'runtime: function' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object lblCurrentCategory: TLabel + Left = 478 + Top = 27 + Width = 86 + Height = 13 + Alignment = taRightJustify + Anchors = [akTop, akRight] + Caption = 'runtime: category' + Font.Charset = DEFAULT_CHARSET + Font.Color = clGrayText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + end + end +end diff --git a/G940LEDControl/Forms/ButtonFunctionFrm.pas b/G940LEDControl/Forms/ButtonFunctionFrm.pas new file mode 100644 index 0000000..a1e81bf --- /dev/null +++ b/G940LEDControl/Forms/ButtonFunctionFrm.pas @@ -0,0 +1,498 @@ +unit ButtonFunctionFrm; + +interface +uses + Generics.Collections, + System.Classes, + Vcl.Controls, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.Graphics, + Vcl.StdCtrls, + Winapi.Messages, + + VirtualTrees, + + LEDColorIntf, + LEDFunctionIntf, + LEDStateIntf, + Profile; + + +type + TStateControlInfo = class; + TStateControlInfoList = TObjectList; + + + TButtonFunctionForm = class(TForm) + pnlButtons: TPanel; + btnOK: TButton; + btnCancel: TButton; + vstFunctions: TVirtualStringTree; + pnlFunction: TPanel; + pnlName: TPanel; + lblFunctionName: TLabel; + lblCategoryName: TLabel; + lblHasStates: TLabel; + lblNoStates: TLabel; + sbStates: TScrollBox; + pnlHeader: TPanel; + bvlHeader: TBevel; + lblButton: TLabel; + lblCurrentAssignment: TLabel; + lblCurrentFunction: TLabel; + lblCurrentCategory: TLabel; + bvlFooter: TBevel; + + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure vstFunctionsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + procedure vstFunctionsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); + procedure vstFunctionsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); + procedure vstFunctionsIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: string; var Result: Integer); + procedure btnOKClick(Sender: TObject); + private + FProfile: TProfile; + FButtonIndex: Integer; + FButton: TProfileButton; + FCurrentProvider: ILEDFunctionProvider; + FCurrentFunction: ILEDFunction; + FSelectedProvider: ILEDFunctionProvider; + FSelectedFunction: ILEDFunction; + FStateControls: TStateControlInfoList; + protected + procedure Initialize(AProfile: TProfile; AButtonIndex: Integer); + + procedure LoadFunctions; + procedure SetFunction(AProvider: ILEDFunctionProvider; AFunction: ILEDFunction); + + procedure LoadStates(AProvider: ILEDFunctionProvider; AFunction: ILEDMultiStateFunction); + + property Button: TProfileButton read FButton; + property CurrentProvider: ILEDFunctionProvider read FCurrentProvider; + property CurrentFunction: ILEDFunction read FCurrentFunction; + property SelectedProvider: ILEDFunctionProvider read FSelectedProvider; + property SelectedFunction: ILEDFunction read FSelectedFunction; + + property Profile: TProfile read FProfile; + property ButtonIndex: Integer read FButtonIndex; + public + class function Execute(AProfile: TProfile; AButtonIndex: Integer): Boolean; + end; + + + TStateControlInfo = class(TObject) + private + FState: ILEDState; + FStateLabel: TLabel; + FComboBox: TComboBox; + public + constructor Create(AState: ILEDState; AStateLabel: TLabel; AComboBox: TComboBox); + destructor Destroy; override; + + property State: ILEDState read FState; + property StateLabel: TLabel read FStateLabel; + property ComboBox: TComboBox read FComboBox; + end; + + +implementation +uses + System.Math, + System.SysUtils, + Winapi.Windows, + + LEDFunctionRegistry, + LEDResources; + + +type + TFunctionNodeType = (ntCategory, ntFunction); + TFunctionNodeData = record + NodeType: TFunctionNodeType; + Provider: ILEDFunctionProvider; + LEDFunction: ILEDFunction; + end; + + PFunctionNodeData = ^TFunctionNodeData; + + + TStateNodeData = record + State: ILEDState; + Color: TLEDColor; + end; + + PStateNodeData = ^TStateNodeData; + + +const + ColumnState = 0; + ColumnColor = 1; + + +{$R *.dfm} + + +{ TButtonFunctionForm } +class function TButtonFunctionForm.Execute(AProfile: TProfile; AButtonIndex: Integer): Boolean; +begin + with Self.Create(nil) do + try + Initialize(AProfile, AButtonIndex); + Result := (ShowModal = mrOk); + finally + Free; + end; +end; + +procedure TButtonFunctionForm.FormCreate(Sender: TObject); +begin + FStateControls := TStateControlInfoList.Create(True); + + vstFunctions.NodeDataSize := SizeOf(TFunctionNodeData); + + lblButton.Caption := ''; + lblCurrentCategory.Caption := ''; + lblCurrentFunction.Caption := ''; + lblCategoryName.Caption := ''; + lblFunctionName.Caption := ''; +end; + + +procedure TButtonFunctionForm.FormDestroy(Sender: TObject); +begin + FreeAndNil(FStateControls); +end; + + +procedure TButtonFunctionForm.LoadFunctions; +var + categoryNodes: TDictionary; + + function GetCategoryNode(AProvider: ILEDFunctionProvider; AFunction: ILEDFunction): PVirtualNode; + var + category: string; + nodeData: PFunctionNodeData; + + begin + category := AFunction.GetCategoryName; + + if not categoryNodes.ContainsKey(category) then + begin + Result := vstFunctions.AddChild(nil); + Include(Result^.States, vsExpanded); + + nodeData := vstFunctions.GetNodeData(Result); + nodeData^.NodeType := ntCategory; + nodeData^.Provider := AProvider; + nodeData^.LEDFunction := AFunction; + + categoryNodes.Add(category, Result); + end else + Result := categoryNodes.Items[category]; + end; + +var + node: PVirtualNode; + nodeData: PFunctionNodeData; + provider: ILEDFunctionProvider; + ledFunction: ILEDFunction; + isCurrentProvider: Boolean; + +begin + vstFunctions.BeginUpdate; + try + vstFunctions.Clear; + + categoryNodes := TDictionary.Create; + try + for provider in TLEDFunctionRegistry.Providers do + begin + isCurrentProvider := Assigned(CurrentProvider) and (provider.GetUID = CurrentProvider.GetUID); + + for ledFunction in provider do + begin + node := vstFunctions.AddChild(GetCategoryNode(provider, ledFunction)); + nodeData := vstFunctions.GetNodeData(node); + + nodeData^.NodeType := ntFunction; + nodeData^.Provider := provider; + nodeData^.LEDFunction := ledFunction; + + if isCurrentProvider and Assigned(CurrentFunction) and (ledFunction.GetUID = CurrentFunction.GetUID) then + vstFunctions.Selected[node] := True; + end; + end; + finally + FreeAndNil(categoryNodes); + end; + finally + vstFunctions.EndUpdate; + end; +end; + + +procedure TButtonFunctionForm.SetFunction(AProvider: ILEDFunctionProvider; AFunction: ILEDFunction); +var + multiStateFunction: ILEDMultiStateFunction; + +begin + FSelectedProvider := AProvider; + FSelectedFunction := AFunction; + + lblCategoryName.Caption := SelectedFunction.GetCategoryName; + lblFunctionName.Caption := SelectedFunction.GetDisplayName; + + if Supports(SelectedFunction, ILEDMultiStateFunction, multiStateFunction) then + begin + lblNoStates.Visible := False; + lblHasStates.Visible := True; + + LoadStates(AProvider, multiStateFunction); + sbStates.Visible := True; + end else + begin + lblNoStates.Visible := True; + lblHasStates.Visible := False; + + sbStates.Visible := False; + FStateControls.Clear; + end; +end; + + +procedure TButtonFunctionForm.Initialize(AProfile: TProfile; AButtonIndex: Integer); +begin + FProfile := AProfile; + FButtonIndex := AButtonIndex; + FButton := nil; + FCurrentProvider := nil; + FCurrentFunction := nil; + + lblButton.Caption := 'P' + IntToStr(Succ(ButtonIndex)); + + if Profile.HasButton(ButtonIndex) then + begin + FButton := Profile.Buttons[ButtonIndex]; + FCurrentProvider := TLEDFunctionRegistry.Find(Button.ProviderUID); + + if Assigned(CurrentProvider) then + FCurrentFunction := CurrentProvider.Find(Button.FunctionUID); + end; + + LoadFunctions; + + if Assigned(CurrentFunction) then + begin + lblCurrentCategory.Caption := CurrentFunction.GetCategoryName + ': '; + lblCurrentFunction.Caption := CurrentFunction.GetDisplayName; + + lblCurrentCategory.Left := lblCurrentFunction.Left - lblCurrentCategory.Width; + + SetFunction(CurrentProvider, CurrentFunction); + end else + begin + lblCurrentCategory.Caption := ''; + lblCurrentFunction.Caption := 'Unassigned'; + end; +end; + + +procedure TButtonFunctionForm.LoadStates(AProvider: ILEDFunctionProvider; AFunction: ILEDMultiStateFunction); + + procedure FillColorComboBox(AComboBox: TComboBox; ASelectedColor: TLEDColor); + var + color: TLEDColor; + itemIndex: Integer; + + begin + AComboBox.Items.BeginUpdate; + try + AComboBox.Items.Clear; + + for color := Low(TLEDColor) to High(TLEDColor) do + begin + itemIndex := AComboBox.Items.AddObject(LEDColorDisplayName[color], TObject(color)); + + if color = ASelectedColor then + AComboBox.ItemIndex := itemIndex; + end; + finally + AComboBox.Items.EndUpdate; + end; + end; + + +var + state: ILEDState; + stateLabel: TLabel; + colorCombobox: TComboBox; + comboBoxWidth: Integer; + currentY: Integer; + selectedColor: TLEDColor; + isCurrent: Boolean; + +begin + FStateControls.Clear; + + currentY := 0; + comboBoxWidth := sbStates.ClientWidth div 2; + + isCurrent := Assigned(CurrentProvider) and (AProvider.GetUID = CurrentProvider.GetUID) and + Assigned(CurrentFunction) and (AFunction.GetUID = CurrentFunction.GetUID); + + for state in AFunction do + begin + stateLabel := TLabel.Create(nil); + stateLabel.AutoSize := False; + stateLabel.Caption := state.GetDisplayName; + stateLabel.EllipsisPosition := epEndEllipsis; + stateLabel.Left := 0; + stateLabel.Top := currentY + 4; + stateLabel.Width := comboBoxWidth - 8; + stateLabel.Parent := sbStates; + + colorCombobox := TComboBox.Create(nil); + colorCombobox.DropDownCount := Length(LEDColorDisplayName); + colorCombobox.Style := csDropDownList; + colorCombobox.Left := sbStates.ClientWidth - comboBoxWidth; + colorCombobox.Top := currentY; + colorCombobox.Width := comboBoxWidth; + colorCombobox.Parent := sbStates; + + if (not isCurrent) or (not Button.GetStateColor(state.GetUID, selectedColor)) then + selectedColor := state.GetDefaultColor; + + FillColorComboBox(colorComboBox, selectedColor); + + FStateControls.Add(TStateControlInfo.Create(state, stateLabel, colorCombobox)); + Inc(currentY, colorCombobox.Height + 8); + end; +end; + + +procedure TButtonFunctionForm.btnOKClick(Sender: TObject); +var + multiStateFunction: ILEDMultiStateFunction; + stateControlInfo: TStateControlInfo; + comboBox: TComboBox; + color: TLEDColor; + +begin + if not Assigned(Button) then + FButton := Profile.Buttons[ButtonIndex]; + + Button.ProviderUID := SelectedProvider.GetUID; + Button.FunctionUID := SelectedFunction.GetUID; + + Button.ClearStateColors; + if Supports(SelectedFunction, ILEDMultiStateFunction, multiStateFunction) then + begin + for stateControlInfo in FStateControls do + begin + comboBox := stateControlInfo.ComboBox; + if comboBox.ItemIndex > -1 then + begin + color := TLEDColor(comboBox.Items.Objects[comboBox.ItemIndex]); + Button.SetStateColor(stateControlInfo.State.GetUID, color); + end; + end; + end; + + ModalResult := mrOk; +end; + + +procedure TButtonFunctionForm.vstFunctionsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); +var + nodeData: PFunctionNodeData; + functionNode: PVirtualNode; + +begin + if Assigned(Node) then + begin + nodeData := Sender.GetNodeData(Node); + + if nodeData^.NodeType = ntCategory then + begin + { Get first child (function) node instead } + functionNode := Sender.GetFirstChild(Node); + if not Assigned(functionNode) then + exit; + + nodeData := Sender.GetNodeData(functionNode); + end; + + SetFunction(nodeData^.Provider, nodeData^.LEDFunction); + end; +end; + + +procedure TButtonFunctionForm.vstFunctionsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + TextType: TVSTTextType; var CellText: string); +var + nodeData: PFunctionNodeData; + +begin + nodeData := Sender.GetNodeData(Node); + + case nodeData^.NodeType of + ntCategory: CellText := nodeData^.LEDFunction.GetCategoryName; + ntFunction: CellText := nodeData^.LEDFunction.GetDisplayName; + end; +end; + + +procedure TButtonFunctionForm.vstFunctionsIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; + const SearchText: string; var Result: Integer); +var + nodeData: PFunctionNodeData; + displayName: string; + +begin + nodeData := Sender.GetNodeData(Node); + + if nodeData^.NodeType = ntFunction then + begin + displayName := nodeData^.LEDFunction.GetDisplayName; + Result := StrLIComp(PChar(displayName), PChar(SearchText), Min(Length(displayName), Length(searchText))); + end else + Result := -1; +end; + + +procedure TButtonFunctionForm.vstFunctionsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); +var + nodeData: PFunctionNodeData; + +begin + nodeData := Sender.GetNodeData(Node); + + if nodeData^.NodeType = ntCategory then + TargetCanvas.Font.Style := [fsBold] + else + TargetCanvas.Font.Style := []; +end; + + +{ TStateControlInfo } +constructor TStateControlInfo.Create(AState: ILEDState; AStateLabel: TLabel; AComboBox: TComboBox); +begin + inherited Create; + + FState := AState; + FStateLabel := AStateLabel; + FComboBox := AComboBox; +end; + + +destructor TStateControlInfo.Destroy; +begin + FreeAndNil(FComboBox); + FreeAndNil(FStateLabel); + + inherited Destroy; +end; + +end. diff --git a/G940LEDControl/Forms/ButtonSelectFrm.dfm b/G940LEDControl/Forms/ButtonSelectFrm.dfm index 461ceda..9ea0bce 100644 --- a/G940LEDControl/Forms/ButtonSelectFrm.dfm +++ b/G940LEDControl/Forms/ButtonSelectFrm.dfm @@ -67,7 +67,6 @@ object ButtonSelectForm: TButtonSelectForm Default = True ModalResult = 1 TabOrder = 1 - ExplicitTop = 94 end object btnCancel: TButton Left = 401 @@ -79,7 +78,6 @@ object ButtonSelectForm: TButtonSelectForm Caption = 'Cancel' ModalResult = 2 TabOrder = 2 - ExplicitTop = 94 end object edtButton: TEdit Left = 80 diff --git a/G940LEDControl/Forms/ButtonSelectFrm.pas b/G940LEDControl/Forms/ButtonSelectFrm.pas index 575a45c..deeeb88 100644 --- a/G940LEDControl/Forms/ButtonSelectFrm.pas +++ b/G940LEDControl/Forms/ButtonSelectFrm.pas @@ -116,7 +116,7 @@ begin info.InstanceGUID := lpddi.guidInstance; info.ProductGUID := lpddi.guidProduct; - items.AddObject(String(lpddi.tszProductName), info); + items.AddObject(string(lpddi.tszProductName), info); Result := True; end; diff --git a/G940LEDControl/Forms/MainFrm.dfm b/G940LEDControl/Forms/MainFrm.dfm index f63ad32..e3e52ee 100644 --- a/G940LEDControl/Forms/MainFrm.dfm +++ b/G940LEDControl/Forms/MainFrm.dfm @@ -1,11 +1,12 @@ object MainForm: TMainForm Left = 0 Top = 0 + ActiveControl = cmbProfiles BorderIcons = [biSystemMenu, biMinimize] BorderStyle = bsSingle Caption = 'G940 LED Control' - ClientHeight = 513 - ClientWidth = 465 + ClientHeight = 548 + ClientWidth = 466 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -14,438 +15,365 @@ object MainForm: TMainForm Font.Style = [] OldCreateOrder = False Position = poScreenCenter - OnCloseQuery = FormCloseQuery OnCreate = FormCreate + OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 - object pcConnections: TPageControl + object PageControl: TPageControl AlignWithMargins = True Left = 8 - Top = 80 - Width = 449 - Height = 425 + Top = 60 + Width = 450 + Height = 480 Margins.Left = 8 Margins.Top = 8 Margins.Right = 8 Margins.Bottom = 8 ActivePage = tsFSX Align = alClient - TabOrder = 1 + TabOrder = 0 object tsFSX: TTabSheet - Caption = 'Flight Simulator X' - object gbFSXConnection: TGroupBox - AlignWithMargins = True - Left = 6 - Top = 6 - Width = 429 - Height = 63 - Margins.Left = 6 - Margins.Top = 6 - Margins.Right = 6 - Margins.Bottom = 0 - Align = alTop - Caption = ' Connection ' - TabOrder = 0 - object lblFSXLocal: TLabel - Left = 12 - Top = 29 - Width = 24 - Height = 13 - Caption = 'Local' - end - object btnFSXConnect: TButton - Left = 69 - Top = 24 - Width = 75 - Height = 25 - Caption = '&Connect' - TabOrder = 0 - OnClick = btnFSXConnectClick - end - object btnFSXDisconnect: TButton - Left = 150 - Top = 24 - Width = 75 - Height = 25 - Caption = '&Disconnect' - Enabled = False - TabOrder = 1 - OnClick = btnFSXDisconnectClick - end + Caption = 'Configuration' + DesignSize = ( + 442 + 452) + object lblP1Function: TLabel + Left = 64 + Top = 73 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 end - object pcFSXOptions: TPageControl - AlignWithMargins = True - Left = 6 - Top = 75 - Width = 429 - Height = 316 - Margins.Left = 6 - Margins.Top = 6 - Margins.Right = 6 - Margins.Bottom = 6 - ActivePage = tsFSXLEDButtons - Align = alClient + object lblP1Category: TLabel + Left = 64 + Top = 89 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP2Function: TLabel + Left = 64 + Top = 120 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP2Category: TLabel + Left = 64 + Top = 136 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP3Function: TLabel + Left = 64 + Top = 167 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP3Category: TLabel + Left = 64 + Top = 183 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP4Function: TLabel + Left = 64 + Top = 214 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP4Category: TLabel + Left = 64 + Top = 230 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP5Function: TLabel + Left = 64 + Top = 261 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP5Category: TLabel + Left = 64 + Top = 277 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP6Function: TLabel + Left = 64 + Top = 308 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP6Category: TLabel + Left = 64 + Top = 324 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP7Function: TLabel + Left = 64 + Top = 355 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP7Category: TLabel + Left = 64 + Top = 371 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblP8Function: TLabel + Left = 64 + Top = 402 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: function]' + EllipsisPosition = epEndEllipsis + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + ExplicitWidth = 364 + end + object lblP8Category: TLabel + Left = 64 + Top = 418 + Width = 365 + Height = 13 + Anchors = [akLeft, akTop, akRight] + AutoSize = False + Caption = '[runtime: category]' + EllipsisPosition = epEndEllipsis + ExplicitWidth = 364 + end + object lblProfile: TLabel + Left = 11 + Top = 19 + Width = 30 + Height = 13 + Caption = 'Profile' + end + object bvlProfiles: TBevel + Left = 11 + Top = 52 + Width = 474 + Height = 13 + Shape = bsTopLine + end + object btnP1: TButton + Left = 11 + Top = 67 + Width = 41 + Height = 41 + Caption = 'P&1' + TabOrder = 3 + end + object btnP2: TButton + Left = 11 + Top = 114 + Width = 41 + Height = 41 + Caption = 'P&2' + TabOrder = 4 + end + object btnP3: TButton + Left = 11 + Top = 161 + Width = 41 + Height = 41 + Caption = 'P&3' + TabOrder = 5 + end + object btnP4: TButton + Left = 11 + Top = 208 + Width = 41 + Height = 41 + Caption = 'P&4' + TabOrder = 6 + end + object btnP5: TButton + Left = 11 + Top = 255 + Width = 41 + Height = 41 + Caption = 'P&5' + TabOrder = 7 + end + object btnP6: TButton + Left = 11 + Top = 302 + Width = 41 + Height = 41 + Caption = 'P&6' + TabOrder = 8 + end + object btnP7: TButton + Left = 11 + Top = 349 + Width = 41 + Height = 41 + Caption = 'P&7' + TabOrder = 9 + end + object btnP8: TButton + Left = 11 + Top = 396 + Width = 41 + Height = 41 + Caption = 'P&8' + TabOrder = 10 + end + object cmbProfiles: TComboBox + Left = 64 + Top = 16 + Width = 208 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + Sorted = True + TabOrder = 0 + OnClick = cmbProfilesClick + end + object btnSaveProfile: TButton + Left = 279 + Top = 15 + Width = 72 + Height = 23 + Anchors = [akTop, akRight] + Caption = 'Sa&ve As...' TabOrder = 1 - object tsFSXLEDButtons: TTabSheet - Caption = 'LED Buttons' - object gbFSXButtons: TGroupBox - AlignWithMargins = True - Left = 6 - Top = 6 - Width = 409 - Height = 251 - Margins.Left = 6 - Margins.Top = 6 - Margins.Right = 6 - Margins.Bottom = 6 - Align = alTop - Caption = ' Button configuration ' - TabOrder = 0 - DesignSize = ( - 409 - 251) - object lblFSXP1: TLabel - Left = 12 - Top = 27 - Width = 12 - Height = 13 - Caption = 'P1' - end - object lblFSXP2: TLabel - Left = 12 - Top = 54 - Width = 12 - Height = 13 - Caption = 'P2' - end - object lblFSXP3: TLabel - Left = 12 - Top = 81 - Width = 12 - Height = 13 - Caption = 'P3' - end - object lblFSXP4: TLabel - Left = 12 - Top = 108 - Width = 12 - Height = 13 - Caption = 'P4' - end - object lblFSXP5: TLabel - Left = 12 - Top = 135 - Width = 12 - Height = 13 - Caption = 'P5' - end - object lblFSXP6: TLabel - Left = 12 - Top = 162 - Width = 12 - Height = 13 - Caption = 'P6' - end - object lblFSXP7: TLabel - Left = 12 - Top = 189 - Width = 12 - Height = 13 - Caption = 'P7' - end - object lblFSXP8: TLabel - Left = 12 - Top = 216 - Width = 12 - Height = 13 - Caption = 'P8' - end - object cmbFSXP1: TComboBoxEx - Left = 69 - Top = 24 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - TabOrder = 0 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP2: TComboBoxEx - Tag = 1 - Left = 69 - Top = 50 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 1 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP3: TComboBoxEx - Tag = 2 - Left = 69 - Top = 78 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 2 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP4: TComboBoxEx - Tag = 3 - Left = 69 - Top = 105 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 3 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP5: TComboBoxEx - Tag = 4 - Left = 69 - Top = 131 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 4 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP6: TComboBoxEx - Tag = 5 - Left = 69 - Top = 159 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 5 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP7: TComboBoxEx - Tag = 6 - Left = 69 - Top = 186 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 6 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - object cmbFSXP8: TComboBoxEx - Tag = 7 - Left = 69 - Top = 213 - Width = 328 - Height = 22 - ItemsEx = <> - Style = csExDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 7 - OnChange = FunctionComboBoxChange - DropDownCount = 20 - end - end - end - object tsFSXExtra: TTabSheet - Caption = 'Extra' - ImageIndex = 1 - TabVisible = False - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 0 - ExplicitHeight = 0 - object GroupBox1: TGroupBox - AlignWithMargins = True - Left = 6 - Top = 6 - Width = 409 - Height = 171 - Margins.Left = 6 - Margins.Top = 6 - Margins.Right = 6 - Margins.Bottom = 0 - Align = alTop - Caption = ' Zoom ' - TabOrder = 0 - object lblFSXToggleZoomButton: TLabel - Left = 57 - Top = 56 - Width = 77 - Height = 13 - Caption = 'Joystick button:' - end - object lblFSXZoomDepressed: TLabel - Left = 59 - Top = 111 - Width = 151 - Height = 13 - Caption = 'Zoom level (button depressed):' - end - object lblFSXZoomPressed: TLabel - Left = 59 - Top = 142 - Width = 139 - Height = 13 - Caption = 'Zoom level (button pressed):' - end - object lblFSXToggleZoomButtonName: TLabel - Left = 57 - Top = 75 - Width = 305 - Height = 13 - AutoSize = False - Caption = '[runtime]' - EllipsisPosition = epEndEllipsis - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - end - object cbFSXToggleZoom: TCheckBox - Left = 16 - Top = 24 - Width = 161 - Height = 17 - Caption = ' Toggle zoom level' - Checked = True - State = cbChecked - TabOrder = 0 - end - object btnFSXToggleZoom: TButton - Left = 368 - Top = 70 - Width = 34 - Height = 25 - Caption = '...' - TabOrder = 1 - OnClick = btnFSXToggleZoomClick - end - object cmbFSXZoomDepressed: TComboBox - Left = 288 - Top = 106 - Width = 114 - Height = 21 - Style = csDropDownList - DropDownCount = 20 - ItemIndex = 5 - TabOrder = 2 - Text = '80%' - Items.Strings = ( - '30%' - '40%' - '50%' - '60%' - '70%' - '80%' - '90%' - '100%' - '110%' - '120%' - '130%' - '140%' - '150%' - '175%' - '200%' - '250%' - '300%' - '400%') - end - object cmbFSXZoomPressed: TComboBox - Left = 288 - Top = 133 - Width = 114 - Height = 21 - Style = csDropDownList - DropDownCount = 20 - ItemIndex = 16 - TabOrder = 3 - Text = '300%' - Items.Strings = ( - '30%' - '40%' - '50%' - '60%' - '70%' - '80%' - '90%' - '100%' - '110%' - '120%' - '130%' - '140%' - '150%' - '175%' - '200%' - '250%' - '300%' - '400%') - end - end - object GroupBox2: TGroupBox - AlignWithMargins = True - Left = 6 - Top = 183 - Width = 409 - Height = 98 - Margins.Left = 6 - Margins.Top = 6 - Margins.Right = 6 - Margins.Bottom = 0 - Align = alTop - Caption = ' Engine thrust match ' - TabOrder = 1 - object TLabel - Left = 104 - Top = 40 - Width = 201 - Height = 13 - Caption = 'Sorry, configuration not implemented yet!' - end - object TLabel - Left = 120 - Top = 59 - Width = 160 - Height = 13 - Caption = 'Engine 1 links to 4, engine 2 to 3.' - end - end - end + OnClick = btnSaveProfileClick + end + object btnDeleteProfile: TButton + Left = 357 + Top = 15 + Width = 72 + Height = 23 + Anchors = [akTop, akRight] + Caption = '&Delete' + TabOrder = 2 + OnClick = btnDeleteProfileClick end end object tsAbout: TTabSheet Caption = 'About' ImageIndex = 1 - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 0 - ExplicitHeight = 0 object lblVersionCaption: TLabel Left = 16 Top = 67 @@ -460,7 +388,7 @@ object MainForm: TMainForm Height = 13 Caption = 'lblVersion' end - object Label1: TLabel + object lblProductName: TLabel Left = 16 Top = 16 Width = 96 @@ -473,7 +401,7 @@ object MainForm: TMainForm Font.Style = [fsBold] ParentFont = False end - object Label2: TLabel + object lblCopyright: TLabel Left = 16 Top = 35 Width = 95 @@ -536,6 +464,7 @@ object MainForm: TMainForm Height = 17 Caption = ' Automatically check for &updates' TabOrder = 2 + OnClick = cbCheckUpdatesClick end object btnCheckUpdates: TButton Left = 344 @@ -548,400 +477,537 @@ object MainForm: TMainForm end end end - object pnlG940: TPanel + object pnlState: TPanel AlignWithMargins = True - Left = 8 - Top = 8 - Width = 449 - Height = 64 - Margins.Left = 8 - Margins.Top = 8 - Margins.Right = 8 - Margins.Bottom = 0 + Left = 3 + Top = 3 + Width = 460 + Height = 46 Align = alTop BevelOuter = bvNone - TabOrder = 0 - DesignSize = ( - 449 - 64) - object imgStateFound: TImage - Left = 0 - Top = 0 - Width = 64 - Height = 64 - AutoSize = True - Picture.Data = { - 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000400000 - 00400806000000AA6971DE0000001974455874536F6674776172650041646F62 - 6520496D616765526561647971C9653C000016424944415478DAED5B09745465 - 96BEB5A4925452492A5B5565237B422A1B842D203620A233CD32EA8C23DD6D6B - AB38CCA0D29E565BB6695A5C41A0C1BD955D96398D0B22626B1F1515B121812C - 908DAC55D993CA5AD9AA2A5573BFBFF26241E3B4A860E6D87FCE3BAFB6B7DCEF - FFEEBDDFBDFF8BCCE572251251276F72FA710D276F5A1903A01D01E0C7380400 - 61FCA2ED87BE931F6884FD03801F000095D56AD5DBEDF6009BCDE6373C3CECCD - AFB179F1A6181C1854A9FDD4D69494943F0F0E0E6A9B9B9B93F9774AFE4EEE74 - 3A8937194E2297CB9D0A85C2A1542A6D5E5E5E761F1F9FDEB0B0B07A7EEF18B3 - 00B4B6B61ADFF8D3C16D1D1D1DE90EC7B0920D53381C0EF9D0D0909C8DA5FEFE - 7EEAEAEAC2DE71DFFDF7AD292D29FDF7828282F49E9E1E396FD4D7DF47B6211B - F131C4C091D3E574D96D76676868A833332B7378CE7573FE7CFBEDB7DFC6C0D8 - C61C00ED6DED098FFDFEB1BF984CA6581F6F1F72F11F06661506F17D505F5F1F - 353535512783306DEA54F1596F6F2F3163686060801828F15B6683D8982D1417 - 1F4773E7CEA5F0F070EAE7DFDC7DCF5D130D06C399310500A8FEE8238F7E78FC - F8F15C3F3F3F61984C26137B00803DCF1A7577775375753585EB741415192900 - 81D1D860B09875B71BE09C949E9E4E1919198239CC2A8A8D8BA5DB7EB6F8E149 - 3939CF8E2900DE39F4CEFD2B57ACDC0AE361288CF71CECCFE2B39A9A1A31E3E3 - C68D1B3512332EB1447A8DCF636262C4CC9796968A0DAF333233282030E0FC2D - B7DC325BA3D1348C0900F88695BF7E60F9F1FCFCD353008064ACB461001450BF - ACBC8CB45A2D2527258B59C78C63F3A43DDE0380D4D454CA9934897C7C7D68FB - 6BDBE8D65B6FA5B884381CE75ABC787136BB44D19800A0B7B747F75F4B979570 - 700BF6F5F51500C0600C4F269CCA3B4595559594919E41498949170020190F20 - 603C3ECBC9C911B1019F7FF4F14774F34D37D3D4695369F69CD90F2425253DCF - A7748D09001A1A1A8CCBEF5F9ECF37EFCD83384D11A7AD5157C01E061D3B768C - 6A4C35343967F2280012ED3D37F83B8E9F316306718AA4BADA3AAA33D751CEC4 - 1C321AD368C97FDC3B272222E2E36F787B571E80A2A2A2EB1F79E8910F70D300 - 007B80800D6C50A954C2D88F3FF998EA1BEA39FA4FA3F8B878425AF47401C90D - F0398E4B4B4B63838D545656466016B24A5FAF951EFCCD83F35252533F1C3300 - 7CFED967B7AE5CB1EA7FD46AB530569A7D0908EC01C0B14F8F51635323CD983E - 8362A262687068F082E007E3A50088E358F40800CE9E3DCB2039E991471FA1A6 - C6C6F6DCE9D37374BA70D39801E0C30F3EB86BD5CAD5DBFCFDFD85B19E0C905E - C3A8CF8F7F4EED1DED3463DA74D285EB69C83674010092F801587ABD9E58FC50 - 5D5D1D353636D21D77FC92162C5A4401019A0FF9BB791767991F1480B7DE7CF3 - A1B5BFFBFD06002031C0D305F01EB37DF2E449B19F3471120506068E467C4FE3 - A52D2424443080330BC7042B6D7D6EAB0051A7D79FCBCDCDCDE1CB0E8D190076 - EDDCB5EEA9279F5ACD79799401921B481B025BF9F9722183A74E9E2A7C5A8AFA - 608104060413400300ACFD292F2F8FDADA5A69DE0D375050509073F9F2E54BB3 - B2B35EBD8CDBBBF200BCF4E24BEBD73FB3FE61CCAAA7EF63F304A0BAA69A7AAD - BD8201F8CE53F8783200BFC7ECB3C1545E5E4E26CE0009F189347FE1828655AB - 56A630407D630A802D7FD8B265C3FA0D0F80019E867B02009D5FC10C4030CBCE - CA16E9F162C325198CEF38CD0937816A940223CF7CE1A6CD9B263300F63105C0 - E64D9BB63EFDD433F70300180B0A7BC6007C86D456535BC3CA88282D354D50DD - 53F74B00482E00A98C8DD1A0B0F030FAE2F8173471C28477376CDCB0E0326FEF - CA03F0C273CF3FFD5B1EC1C121C26849FC486048002005028044A6B35424499B - E77B9C030C08E1F379FB780B006A590CCDBDEEBA5797DDBFECDE3107C0892F4E - DCB86CD9B29D0DF50D3A501D0623C849AA102080C22DAD2DE2754C748C384E18 - EE74A1E6170048ACC0315CEE8A18A0F6558B7489E397FEE7D2DF2E5CB470FD98 - 030083357B585161E1A453274F5DC39A7F527151714E7D7D43080CF6E5683ECC - C6593A2DACDE5D141A1C3ACA1430028A1EC64BA0E03BE800082B1457A80A2B2B - 2B69D57FAFBAF99A19D7BC352601B8787475751A8A8A8AB3563EBA6273FEE9FC - 541F6604040E0CD40669C58CE2B55C8178E1454EC7B0384E124F3A9D8E828383 - C566369B893546CBC1370F66325B1CBD3DBDDECCB06EFEAE7FCC0280515559F5 - 935FDDF9AB3D2525E7A255EC0E3DBD3D62D693129284F19DDD9D28A4C4ECC7C6 - 8C6323355C32378A78A1D5060F474545F5474545D6C4C5C79F0C0D0DB35A7B7B - 354DCD4DC96C7C09EB864206AB2C3323B3F417BFFC4533DC6D4C01F0C1FB1F3C - 5C5C5CFCC4C1837FF2AAA8382F440D00C08CC7C5C489C2A6974BDDE6966652F1 - 8CCF9E3D9B6EBCE146AAAEAEA2F3E72BE102676EBAE9A617144A45F7F1CF8FFF - 339AA9D7CEBAF6DD59B3661D429394D9A432D599346FBFF5B67F557555F7DAB5 - 6BBBF506FDA5CAE3AB0B80B5D71AF0EEE1773773D172170C7EE38D3778961B85 - 44C67BCC5474543433C045DD3D5D34C0B31DCC2E71EDAC9F882AB1ABB3536806 - 8E2936DE0F984C260D8B28D9BC79F3F6C425C415707D50C7E9B19CB3441DBB8A - 15D73C72E4880F03E1DCB869A33D2020E06210AE1E001CF4528F1C3EBCDD6EB7 - E56A3401A299B165EB16B2B47790973703D0D3C381CD9722F411C20598D38889 - E2F5D2A54B29392599BABABBA8AFAF9FAC3DBDD4C9604008E1B8969616B14796 - 60306D5AADB6695A6EEEB165F72D5B131E1E66DAB963A7A2BAAA4AF6D8E3EB2E - 6E995F1D00CE9C39F34F1FFDE5A3577D7D7D220F1D3A44164B07F50FF4898686 - 52E1C5D457A073441AF673C85C180D790C46801D6969E3897D9E1C6CA028A038 - 304225221E801152E75800D2DD4336BB8302028368E1A2F9C50F3FFC9B397C8E - F67B97DCAB5C72EF12E79429539C571580A3EF1D7D282F2FFFF1E8C848EFBDFB - F6B21FD7607D80121212841126B349A442748071D3C15AED280032B94C748A7B - 7ABA19086F217E50554203C4C6C68A521800201B203B747676513F33242C3C9C - 8CE9E9AC39E434F7FAEB56DC70C3BCA7BF3CF1A59C63856CE6B53387AF0A006C - 80EF81FD07B6D6D6D4DE83EECDE1C3EF504949A9C8DFA02CD45C9BA55D1867B7 - D90500A121A104C90C60F01EB466FD2798A00DD48A2A108C409A8C8E8E1620E1 - 77A816C10CBB7D98821840B4CBA572392252FFD9E6CD1B67738681E123CAE20A - 03D0D4D814BD7DDBF6DD7CA3B392921239729FA78307DF10378616166E4E5A1F - C00643C18AB0D030A1FC60100C03A5E53239F9F9FB89DC8F5906782884525252 - 04EDE146D87B31438238601AD38D02C892921271DD88088365E7AEED690C7CEB - 256EF5FB07A0A8B068D26BAFBEF63AA7A394969656BEF170D6EAB5C24850B5A8 - A848F8B3541461762D160B718416F4961641F07BA9318AEFB081F2301840800D - 7ECCA66176171F1F35A919502C92A05384365955559588117E6A5FE7CBAFBC38 - 333129E98B2B0EC091778FDC79F8D0E18D757575C1C8E9A02BA42AFC147255EA - 00C3F8D14ACF394C1D1C140D1106CEF9AAD122C94D6937103016511F1523C002 - 2BB014C63A803541041B1D46E9C6740A0D0B15006375498087E33986ACF9DDEA - 658B7FB6F8C52B0600FBB072DBF66DBFFBFCD867ABD38C4621642A2A2AC4CD77 - 777531BDDBC8DA67E51B97896006E1834A0E6B84608190BD6CB054F682099191 - 91D4D6D636BA6CC699843A3A3BF8782FD2EB0C5C04D9983DBE149F104F937272 - 845B15161609A0058B46E434D619391BECDEB8F1D93BAE0800EDEDED219B366F - FDE3F9CACA9B53929349E3A7E674C61BFB29C6F98A4A3A95970711C411DA4A03 - FD0334281A9EEEFA5E21773747008A00865D0200A0629400C0AA11661E8085B3 - 4BC1405FB53FC5C7C7D3848913D8CDC2A8A0A0902ACACF8B0029DA69E822F3B1 - 880F29A9C927DE7CF3E0357C1DE745B7FFDD00282F2BCFDCF487AD2FFBD9ACB9 - B18670B2ABD82755BEA440DB8B8D191AB4513DEBF9C484586A696C26B3A9DEBD - E2CB22C7CAE96AA0DF2A80E9636086871D9CE7DDA52F673F31EB5084727E03F1 - 0377822BD8386304040452744C344D9C982300013BCEB3A40630A3EB8960D348 - B7880596E5C87B87D338905E1C0843BF35002C6C166EDFB9FB95DCE8507D4643 - 15B59C2D25E39C9994A78BA3B25E0745A9B9D61FE8A5E68E6EB2D858DAF6BBFB - FCC8E17E7E6AE1FB2E50B4A34300D5D9D5C972D84A8303FD0290418E1B76D113 - 441F5029D2A39CD982A2289A8BA38939D93CF37A2A3853C099A59C8D1F1C5D37 - 947A8938BF6364696DEDDA350FDEF2AFB7BCC40CF3EC18877C2B0076EED87DDF - E1770E6D08090FF7991D1D4EC98D55A460BAB61B22E81355281902FD28ADCB44 - 75A74F5138DF6C6F5C2A1DADED6661E3A43E2B1A1F2ABE59A63DFB306656C12E - D0DAD62ED82013DD1F6941D4C686D99815288D95423019583F4C98908DF446A7 - 617C6919477BB74B499DE4610030D245827B216DD6D7D701FC73D3A7E77EF2E8 - 8A15EB5887B45C36009C977DB76E79EEC9BF9EFAF4D7FE61323143319111E44F - 6114E8E5478D5DECE3EC65330C8194DAD7448A965AEAF7F1A72F65A154452A76 - 11339536549292293E292D894C6715E492F9927CA88F382F507BDFA060035C40 - EA09628F3259C96EA5D7EB84F18608CC7C11950AE3072F584116C78C000070BD - B9CEE86756595874B5715AC66F76EFDEBDE4E7B7FFFCB5CB7281FAFAFA88679E - 5AFFC7D696869FC6E57851514B2959070768667A1AF5D7710A5386D2B0AC857A - 07BA48ABD65094269C111BA6762E5CCE5B06B8E0B19336A1971A069901321769 - 0682C96689A0047687487339595ADB2922369A4AD421F469451D29C82D9220DB - A0FDE1EBD913B2C860D071B42FA6D212B7F152E7D8F3010AEC11447D38A09A1B - 4C42318E144A22083FBB71C3EABBEFBEFB896F0C00FB59CE9AD56B5E6A696E9D - 3C79CA44F20DE9259B5737073BCE02B52E9EFD78D2EA07C9642FA573D566CACD - 8EA7005B1C59DBC2C8EEECE2A8DF4676A6B28F8F8AB5A882DFDBA8ADD941722F - 352D8E09A294CA52EA31379226399E0EC8FCA96C5046410C0CA2BF7C641D6002 - 1B1F1169A0C282223A77AE74D478CF45130900B5DA4FA44B93B97674995D4868 - FE6D7656E6D9EDDBB72FE2345BFD8D006061B3E0F9E75ED8C1371282A52BBDDE - 400A414939A97DBD69D825E79CEE4B6A6D2F39FD2D6493338D3B5C34D81CCA2A - CC8F9C41662A305793BF9F17658F4BA28E8A403EDE9F025D7DA41876B7F07D38 - 038429896A7AFAA8D2A1A2F15C3B984D26D11182E0C9CECEA4C8E8482A2E3CCB - 2AEFDCE88C7ACE3A06F61A4EA11048B575B51C108784B4F6E5F48999CFCCCE2A - DEB56BC702B6A1EEEF6701E61E2375F78EED3BD673DA090E0A0A142206BE2817 - 6B8F6E55C7DE3A5A5DA854729E6525CF869C733E5F58C36245D74E168785A33E - 0BA67A5FF2B2479351ADA098F66A1AE8B050045785653EA1F476593D69183023 - D7FD90BC260600B2163E1F1D13C50AAF98CE9D2D11C67B2E984A6D736C087610 - 5426531DC727DB081BD4420D721D726EF79EDDF30D0643AD87955FAF030EBF73 - F8AE279F78F2153EA912911485147232F233E804FDCDF5BD0832022F178D0423 - 690183C4C28582B587028FEEB1DF5BDA988E0E17FD4B948612384BF80F769245 - 1940EF0FFA51A9C39B92B97002EDEBCD6691F3B398F6E3C645D3D9E2B3545C7C - 4EE801CF4553C970188A3A0303C0391C765C9AFC35FED4C62A34DD682CDEBD77 - CF02835E5F7791999706802FE2BFE49E25F92C2E9259E9D100073B342B908B47 - D7F9C5D29652F4F1509C88A00335A7F61592177F0EBB8306850FBA7BFC2CE1C5 - EFB55E0A0A241B05CB1CD431E4A06A363E44AFA7D6A666045B0A64AD8080372E - 3686835DA9A03E84130CF37C68C20DBC4B300520C078B176C0E8070469A891C1 - 4C4B1B5FBC67CF1EA6FDDF187F6900CC6673F2334F3DF3DAE9D3A76762D1014F - 6041B448E908C602844056631035FE7EFEC27091CF47567CC018374BDCD2169F - E146A5A035C4C0D8F97C03C2979D94CA121A52B7818DD770D5979595211E782A - 2B2917FA1EA5318CB7E3385682F071F783922EAE0B7442F79B98356E692DA790 - E0208E017594323EB578FFFE7D3FE53862FE9A10270008E517ED7877F4BDA3F3 - 5F79F9951718841818696EA8A7162E3F316B9E6B73D22C6028990578522B4013 - 20000130124BA4AA0EBF517A292F783E00E7828E8066B7D91CD4CD85136A808C - CC7496CEF154CED2B690F57D4FCFC80228723C5F339A75472333C5C6C76281C4 - C60CC3DA0018E670DAF9FA01D4C2339F929A5ABC77DFDE057AC32567FE020042 - D8988ECD9B36AF7C7DCFEB2B38C8F881CE30A2ACA24C94B252F92AEA770E842E - 69B98A68341A4B01092CC1CC8319084AA2CE1F618978308AE308CA598929D8E0 - DBDE1CA9D38C6994989C40D55535ACEF0B447F4F620DF6B11C0FE2E262E9AF27 - F3C5B92197516B60383893201EB5B5B6A01D56B86FEFDE053ABDCE4CFFF71000 - 0472D455DF38EFC61A36DA5B4A2BB8281E5B931E6985C1A8D7BD1442A288FF36 - F07CD6CF73FD6E1414BE29C402188994A809D05050A09B256083B43638CC8133 - 8BA37D567616D5D6D4B2F185D4D5D9258E7757770E5150C1354EB2F11C4D38CB - F45103670B5C1FEEA1F2F6A2D666186F2CDEC7B4E7C2E7EF19FF15004D8D4D7E - 93A74C36A9942A055A5500008B95164E539E03F4C54CCAE55FFD73892700E8B8 - 31E9DD8D3799FB33113F1C5F0102AA4A9D20B88D8175055812C1F5FFF8B4F14C - FB62F1D8AB98F991670273A74F65E002282FAF805D49258AA566369E46DC11F7 - D5DCDC40E9191985FB98F6DFD0F8AF62006F96FDFBF62F3B74E8D06206C388BE - BAB5CFDACDC28703BACCC5741DC69E33421447DA28AE09E4122030447AFA5300 - F215349E6F4687E7834F985D1C9B9591254050B3AB208D4A353DCE9995696425 - 184A79F9057C3D959879F405C14B9C03B46F68AC479EBF5CE34701F0CC020A46 - DFC0AED0EDA5F21A90140E8CC7CD707CF0E7AC909E979797CBDB848282829CCA - CACAF8EEEE6E2F111047A2BF686B21E77DCDC35A12583012C24617AE130F47DA - D978C402E969B07096C053A7E6B0CF9F1631096D310BA7652980C27873BD995D - 23F3CCEB7BF72CE2F35C8EF19704E0B206CF946F4545451A8331F5C48913D30A - 0B0BB31890644B47870F8D640CE93980AF7B744D2C80706035A61947AA3E1547 - 7BA74871D0135A2DC78C202D07B756D13BC09C2023C078AC29188D6945070EEC - 9F7F9933FFFD0070F1604AAA6A6B6B13199029A74E9D9ACA0CC92E292931B6B5 - B569A4B4E9F960C4C83182092949C9A28891C914A3A9169D5EF4FF9B39ADE109 - 32B796B08B42A796E56E464646D108EDBFE983915716808B079F5BC1054D4C7E - 7EFE140663228332A9A8A86802FBB0167E8E219E08E3E0181511457A76058E91 - 0C8052C8D8C4C44432D5D6B146E816B1C1E1B0899947A648CB3016EDDFBF7FA1 - 5EAFABFB0EB778F597C7990D11881DC5C5C559701B569C13ABABAB0D620D3035 - 4D640C9D3E42B4C9F937ACE8AA455CD185E9C4E269554D3565A4A71771AA83BC - FDB633FFC30170F1E8E9E90979FFFDF7E73FBE6EDDCAD696B664A4454E6742CA - 82F6434303E277288EA04B8CC674F8FCC2AFD1F6FFFF0090467B5B9B61DD638F - 6F3A70E0C06D580683DEE8EFEF631DE172477B96BB995999856EDA7FE7991F7B - 00607040F479F2F12736EEDF7FE04E9E7D753B17488E61BBE8065F7FFDF52777 - EDDAF96FDF21E08D7D00A4C1622B85E3C414CE28B17C7F4E0E861573E7CE3DCA - 19C4FA3D5F6A6C02701547D83FFE79FAC7FEEFF3FF0B43E930A009E1CAF90000 - 000049454E44AE426082} - Visible = False - end - object imgStateNotFound: TImage - Left = 0 - Top = 0 - Width = 64 - Height = 64 - AutoSize = True - Picture.Data = { - 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000400000 - 00400806000000AA6971DE0000001974455874536F6674776172650041646F62 - 6520496D616765526561647971C9653C000010B74944415478DAED9B096C5755 - 16C65F77282D5BA150CA2A4B45A050948285418442214E6140C74463C718B463 - C448C6688C6B8C44258A88041D9851B4A22D33A962D906991123B22450108A0A - B21611690B48D94A59DAFF7CBF1B4EE759CB48070AFF89DEE4E5F5FFD6FB7DE7 - 9CEF9C7BEF6B48201068E9795EA5F7CB6C8D424440A35F3A01D1FAA3E26AF7E4 - 2AB5E85F09B80A04849D387122E6ECD9B35167CE9C89ACAAAA0AD3DFE1DA42D9 - 4E9D3A15D6A44993334949493B2B2B2B1B979494C4E93ACE855457577B6CB4D0 - D0D040585858757878785544444475A3468D4EB76EDDFA987E57072D01656565 - F1F9F9F9637FF8E187F873E7CE85024CFB90D3A74F8708AC575151E1959797B3 - AF7EF0C107576CDDBAB5F7A64D9BE28F1D3B16A2CD3B79F2A4A77B3CDDE38938 - C808889840AB56AD02C9C9C981112346ECCCCACACA1731554147C0C183075B3E - F7DC737FF8F6DB6F9B474545D51CC7A200523F1CC003070E3812060E1CE88E1D - 3F7EDC93C778F20C4F44B96B05DA6D1CBBE69A6BBCF4F4742F3E3EDEFD9E3871 - E29C84848403414500AEFED8638F65AD5EBDBA83DCDB010B0909717B08602FAB - 79478F1EF576EFDEEDB569D3C64B4C4C7484009A0DC0E7ADEE363CA177EFDE5E - 9F3E7D9CE7C8ABBCCE9D3B7B77DC71C7F21B6EB8614D501150505030F089279E - 1803788002DEDF14CFEED89E3D7B9CC53B75EA5403128B9B97D8DF1CEFD8B1A3 - B3BCC2C46DFC0D19CD9A353B7CEBADB7E6C4C6C61E0B0A02D4E1D0C993274FDC - B0614322041858DB689082EB6FDBB6CD6BD1A285D7A3470F67752CCEE6777B7E - 43C0B5D75EEBC9D29EC4CF7BEBADB7BCDB6FBFDD8583EE0BC80B662B244A8382 - 008957CC030F3C304971DD58CD1100609ADF13D6AF5FEFEDDAB5CBB975F7EEDD - 7F4480818708C073ECFAEBAF77DAC0F1152B567813264C70BA317CF8F07FE8FE - 757A64202808D8BF7F7FFC430F3D94ADCE87237E4A539ED2564D28B007D0679F - 7DE61517173BAB1A01E6F6FE8D78E7FEC183077B4A91EE1E09AB23A457AF5E5E - 7676764EBB76EDF65C64F71A9E80A2A2A2AE8F3EFA68169D8600F690C0863744 - 46463AB09F7EFA2964392BE2CAA4457F085818709CFBAEBBEE3A0798B0C1B310 - 523CE2E1871F9EA7F0D81534047CFEF9E7BD2480BF8F8E8E7660CDFA46047B08 - C003D081B4B434AF43870EEE985FFC006F02C87D2A7A1C015F7EF9A5234959C6 - FBFEFBEF2B74FF1C6591A34143C0F2E5CB539E7CF2C9713131310EACDF03EC6F - 40AD5AB5CA3B7CF8B023809CCE313F0156FC404CDBB66D3D153FDEDEBD7B01ED - DD7DF7DDDED8B163BDA64D9BEED2B979B5B3CC5525E0C30F3F4C7BF6D9674741 - 8079803F04F88D5BAF5BB7CE81239695CA6A14DF0FDEB6B8B838E701CA2C4E13 - 66CE9CE90813F8B21B6FBCF12F7AEDB9A021E09D77DE19FEE28B2F0E555EAEF1 - 000B03DB00B17DFB765701A6A6A6BA9836D5C70B8C0CE21CD22080F457585848 - 85E98D1A35CA6BDEBC7940E97671BF7EFD36D4A37B0D4FC01B6FBC31F2A5975E - 1A8C55FDB1CFE627800A1011C30338E72F7CFC1EC0F5585F80BD6FBEF9C66580 - AE5DBB7A999999C7146AB344D099A02260C68C19635E7EF9E58178801FB89F00 - 6A783C0080B2A04B8FB5815B19CC39A5391726548D268C7DFBF62D79F5D557FF - 2A022E7620746508983E7DFA98A953A73A02008B0BFB35806368006530E07AF6 - ECE95CDD5FF71B01160294CA6C5C8F37AC59B3C64B4949D93E6DDAB4DC7A76AF - E10998356B56BA52D490962D5B3AD056FC181946006ACE39DCD90649B6F97FF3 - 0C3C80E7A103104036D050788386D08B828E0059A7DBA449937EA72227065707 - 302267552124E0C2A5A5A5EE6F0639343F78DBF002EED170D76900CFE15EB6FB - EFBFFF9FE3C68D5B1D7404D0246E4D366FDEDC4EF57E476DEDB66CD992F0DD77 - DF4503182B028E1A8086C29BA758E3BC91C239EA000A2B065710B673E74EEFA9 - A79EFADB902143B6062501B5DB9123476245429BC71F7F7CB472792B2C69951F - 96B522C8C412CBD3AC7862BE801060DBB76F9FA71AE3C4071F7CF067DD532D61 - 0CD7F32A75EE6CD0124093D53ADF73CF3DE3BFFEFAEB661448283A56EFD6AD9B - 034F4DC0D800EB2378145294CAE88586CCD5EDDBB73FABADBC4B972EFBA50367 - 747FA4CEC709FC41D50DA522EB507272F2C1ACACAC13FE19A8A020E0E38F3F1E - 2C0F189E9F9F1F46FA230C98F3C3E280A5511330DAC3E237DF7CB3377AF46857 - 2BECD8B1831038307EFCF8F5BABE72F5EAD5DD994CBDE9A69BB60F1B366C1B93 - A4F2A6300963D4471F7D14A92176A52AD1D3D28DBA86C757960059296AF1E2C5 - A3A5F829585C6EEBAC8C0740009662208407303D76DEDA9EC07983060D22745C - CD2072AAB43FAB22288A224A9560914690251A1F948BC0C3CA12E522CE15444B - 962C095FB0604140E9B85A6385DA245C3902247AAD047E9CACD5819A000BBFF6 - DA6B4EFC8C00840D858700CE5BFA93C2BB592248619E10F220833DF79141D8A3 - 157A5695483BAE31C15E659F151A581D7DFBEDB743F19E2953A6D49E32BF3204 - 7CF1C517DD3FF9E4934CC567D3828202071ACBE1E25611020062C8EB80E63C1E - 01398CFD15EFAE32B401147FE3217884CD1CB38724CE51296A8458FAC8238FE4 - E81915F7DD775F6876767640630DBF17343C014B972E4D93D20F4F4C4C0CCFCD - CD75715C5656E60A1E3A4A2D4F16C0B2741A973702104500410E449022114332 - 0533C0144F1040368014BC82FB184E33B50659E9E9E9FFCAC8C858B576EDDA10 - 1657860E1DEAF7828623401D89C8CBCB1B535C5CDC1F0B2E5AB4C893E23B37C7 - 65A9E60E1D3AE4C091F62080313E5E0031FCE61C618027001A022082EBD10AC0 - 721DA345C0721F0402DE86CB7ACF5EC57F8EC80438C545C37B802CD36CEEDCB9 - E3D5D1CECCEFA1DC527CD731A6B0E89CAD0FB00114AF80007E03C8621D2FC0EA - E47EAC0C79784A5252524DA6600F3180679688E74036EF95A6546848FEBA883F - 5947572F3F01547C6FBEF9E604C5762B3A4BC799B80424AE5A5454E4E2D90645 - 58174D90423BA0B608C2F53631CA39365CDE5223DE8037112E841084B22E0078 - A6C998614623742E307BF6ECB932C4BE0627402ADF6FE1C28519CAC18D1136AC - 42A94A9C52AEDA0C30E0FD233D5675507FAEB741129B110158E29B112364718E - 38E75AEEB3F941F6108CCE189168C8D34F3FBDE4CE3BEF5CDF6004B0C829971F - B672E5CAA1740437A6C0A1F37400F7C64D6D1698C207CBB3C70B20827336ECC5 - 13581A63B6C796CD94491C51DCCF58001DE07E6690994431F0100D782BA7A928 - 33333337BFF2CA2B0B1A84000959F48C193332156F3D894B5C1100B82C8D3864 - EA8A7826AE112E5BDC30709000182386FB716B23801218CB738E900220EF017C - 4A4A8AF306859E239D67DB428A4D95AB5FFB5474CDD57B2E6F2124416BA362E6 - B77A46072CE69FF0A0B374944A8F8E12BB0C5C6CC5D714DC88F14F7AD89C8179 - 06E72D0D021011240B6079C0E31D106D96F72D9FBBDF22B34215E1EB22EFE465 - 2340854D524E4E4EA62AB41844C9567588775CDE4675580E006C36DA33F162C3 - ADE938318EB58C189E63738290417A840CF6E84AFFFEFD9D376CDAB4C965165B - 4AF32FA89AC6E00DCF3CF3CCB2DB6EBBAD50863977C90428ADA44AEC4629EEC2 - 496D58CC263B008FDA633152119E41A751661A40F1101A2ECF75FCC6DDFDE5AF - AD035A2C5B98503F306FC81ECB031EB5B7D9639B4CB5E7701F1E43C125F2CBD2 - D2D28A350C5FA9FB4FD49B00B96BC4CC993347AC59B3661031C88B0048CC0292 - C206C6C9FD8400220839581931C4BA780AC7D00BAEB785118E59EAE36F7361F6 - FCB67900621EE537CBDB129A7FF5C808805C368CC3BB300ED7BCFBEEBB0BEFBA - EBAE8DF522408399D8A953A7662AB7F7807D80F072549F0E40080089692B5CE8 - 0416A71E0004D7A0CA3400E1D25C07017490FA002F40CD69361304495C87E5C9 - 009CE79B00DE6FF1EEFF8082BD4DBBA13BF4EBFC40C991316DDAB41513274E5C - 79D104C8D5DA2997DE22208903060CA8C9D1EC01640B1500658292995D3C0200 - 741252B02C1DB23940EEA3433C8FEB2000902839BA611320B60E606E8FDA135A - 06DEBF686204D83A247D316DE037D7F6EDDBB74C293B4F9E7BE4A20850AC27CD - 9A356B9C3A12CD4B713FAC89556C5516F0FE3C0EE374048BD3013A62391B0B43 - 04C0CCC2DC4BA771514285B103314B0641E5D569176A5BB66CF1BEFAEAAB1A8B - FAAD4E630F713CDBC0738C7763793DA754FA95270CE53F2B82744E4CF5D7587A - A4727A63D4DBD6F6ECEB0E63DE1AA4700DE778B9A54513373A0129589A8E11F3 - CC000188B5413ACF971F94BC1040598BE549797EF0FE0553FFCC3162C7FB006F - 7A00B1902AB12E9B376F5EAE0FFC7F2740964F79E1851732F5D050536D9BC707 - 24D6B7C2C508AB9DCBFD9FC1D06CD2028B5A594C27E9305A8178E2F6C42D6105 - 78521EE0C92280F72F9A1A709E41E6A1411CD7D8208AEC229D2A15F8BC5AE02F - 4C801E1079EFBDF7FE51C5451CB189E58869368B2FFFD2165E6184B037E074C4 - DCD096B5B89E67D80713E47C2C8457502C496C5DAD60E0113B08E03AFF52197F - 1BF1780ACF07BC8D35A8442153E1E4C04B3C6B83AF9B00B11F27B51FBB71E3C6 - 4E749E0ED44E4700E0057414962D9F1B21160AE62116EFFE8F9DF81B62792E29 - 11E1033CCF4D4E4EF6BA74E9E2D21C8A6F637EAB0D6CAA9C675A16B12A13F00C - 8BF12A8553695E5E5E2ED3621790B81F13B074E9D21E73E6CCB9450F6B06483A - 8455CC5DED0566059AADF4E01D10421C9A97D8A8CEBCC4FF7D406D42080FC864 - 488B5852DA92EB6D01D45C9F4C409FB897948891006F4632CB03FEFDF7DFAFCB - ED7F4A806E3C357DFAF4DFBCF7DE7B43146791000204CBCF58C986AF66598B3D - 9AA9B109125E82E5113BC8B0713EC7ECC32823C51649B130E7517FD60518F652 - E5418A91C49E90C033F8A28C67D33732058DF3781BA9588257027811F4739FCA - 38021A497523323232260B74B809132F6568E9CF0A663DFBED1739FFFA9D9F14 - 4B991636749CCD74C4A6B2A8F010470A2C2C8F285A6DCFB3F00ACE932D7826A4 - D9822AE7791EE0113CDCFE22BF13FA0F01A9A9A97F12C0502C07013C8C12D6DF - 20C072BE353F01F6DB7FAC36211CB3D12261435D0131E4790A28629EF7FABF09 - 644D80F0628E8F3E18780B478EF15BE153929B9B9B57EF8FA4B49DD28D030A0A - 0AFA287E5A4B4494BA4F54F215F779E5AE66AF8CD0544ADB5471196284587557 - D78749751DF37FF80440EE45F42001F2FD56E77E34C12638B132E0318EB93DC7 - 0803DCBE9EE07F2A826AA1623F46EE7A5AE06A1617CDAAE883B2427C6161617B - 6D0972D50485490BA531F7E9A76984C5FA85BED6B2E380B4696C6A0000E161F6 - 3518C0F96688982784080BD2B20928EF42A845E001E9D7FC7A82AF93807A3559 - 2A42B57B6B9191B876EDDAF6AAD3DB8A9038A534271458D7BE03B81019100010 - 06559666ADBAE337AE4F5AA34CB690B49847FDC9F3F3E7CFBFD898BFBC04D46E - B24A9844AC2584C86AEDE5216D357069AD6A2CCAD2A6FFC388F3F738402C7D61 - 654B9D36C2A40CB6FF21B029731BE8283C4AE5F6FF2BF8CB4F40EDA667872A3E - 9BF1A538E1C2C71112B9B68AE1C6C439CDBE0843046D086D3340A4449B5237A2 - 209034A99847ED2F54E105070175357943ACC8E02B9136848D2ACE84DDBB77C7 - 6255B20040C90C143C94C080E51C3A818730E57D1E7CEE45E4F9E023A0769375 - A3972D5BD663CA9429BF519CC751DDA1FCB8386ECF109CC6E088C50EF2BC62FE - 522D1F3C0458C333444286C0F52614C80636AF6082C7B780E7DDFE522D1F7C04 - D0F89F82E79F7F3E4318FBC9FA110C906C497CE4C891FB737272FE7E098217FC - 045853B1D54A3A9128016CAEFE05248687D3D3D3774800EBF319ECFF2F0157B0 - 45FFFACFD3BFF47F9FFF371E3422DF2CEB57F80000000049454E44AE426082} - end - object lblG940Throttle: TLabel - Left = 79 - Top = 8 - Width = 281 - Height = 25 - Anchors = [akLeft, akTop, akRight] - AutoSize = False - Caption = 'G940 Throttle:' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -21 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - ExplicitWidth = 401 - end - object lblG940ThrottleState: TLabel - Left = 79 - Top = 35 - Width = 281 - Height = 16 - Anchors = [akLeft, akTop, akRight] - AutoSize = False - Caption = 'Searching...' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -13 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - ExplicitWidth = 401 - end - object btnRetry: TButton - Left = 374 - Top = 20 - Width = 75 - Height = 25 - Anchors = [akTop, akRight] - Caption = '&Retry' + TabOrder = 1 + object pnlFSX: TPanel + AlignWithMargins = True + Left = 233 + Top = 3 + Width = 224 + Height = 40 + Align = alRight + BevelOuter = bvNone TabOrder = 0 - Visible = False - OnClick = btnRetryClick + object imgFSXStateNotConnected: TImage + Left = 4 + Top = 4 + Width = 32 + Height = 32 + AutoSize = True + Picture.Data = { + 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000200000 + 00200806000000737A7AF40000001974455874536F6674776172650041646F62 + 6520496D616765526561647971C9653C000005774944415478DAC597C74B6C59 + 10C64F9B737A9815DA8839A388115C28032266915988CE6660FE9199EDB8706F + C4D1850CA2A8288A2898338AA298033EF3336B4F7DA5E77A5B6DB5DF660E1CEE + EDDBB74FFD4ED55755A7353A9D4EFC9F4303008D46A33C282E2E76373535B536 + 333313262626FC9DFC5EFDDE47436E0A57CCC7C747F1F0F08079DEDCDCFC5DFD + 9E02505A5AEA78727252ECECECEC636969C9CF2480BC1A33A461F5F5EAEA4A77 + 7676B6E2E2E2D2DAD0D070AD07909D9DFD5B4040808F56AB153E3E3EC2C2C242 + D9BDDA0B3FE301CCDBDB5BB1B5B525565757C5FAFAFA7C4747C73F0A40414181 + 3BEDFAF798981891989868D0A8B121C0FBD8C8DDDD9DB8BFBFE7E7232323988F + E6E6E67F3535355D33406161A1D6DEDEBE22232343F8FAFAEAC55E6DD41800D2 + 91080B0B131452717979294647471962737353747777E39DEAC6C6C6430620E1 + 31406666A6F0F0F07803606C0820E0F0F070E1E4E4A400F5F7F7731876777745 + 5757171E57D7D7D73F0194949430404A4A8AF0F2F262B7595959899B9B1B1690 + 3100F86D545494A0F594671473B1B4B4C46B6D6F6F8BDEDE5EAC575D5757F704 + 4019C000494949C2DFDF5F242424080707074E9DF3F37341CAE5EBE9E929EFC2 + 50ED00747474B4B0B5B5559EEDEDED89E9E969FE0DD6DBD9D9110303031C0205 + A0ACAC4C4B062BE2E3E35984212121BCD8EB5D630710D4F1F131C715D78B8B0B + 7E6663632362636385B5B5B5F23EA5B5127B590B00303C3C8CCFD5B5B5B52F00 + 8E8E8E15701D42400A156E6E6E9C8E521386C4F69CDF1C77D40F39E0B5B1B131 + 0E230C030020D000A0E8D95B808888083688C5A062188627BCBDBD393BE0DAAF + 68E1F0F0504C4D4D291A820D18070800262626DE07080D0D6500189710EA4A48 + 158C61E0194320070707627C7C9C8DBD2AC30C2135F1068052A622282848B8BB + BB2BC6E57C5D8AE10D84EB3D0800A0D8C0B09CD238E6FEFEBE989B9BC3BDBE08 + 01E0E7E7A700A821A406A4417C4E4F4F17E4B53700D003F25C6D5CC61F6205C0 + E2E2E2FB00308E459182883DC408636A0FE01E9902B11A126667672767897AF7 + 00432A2373500DDF0580F26118C5048AC60484DA1B308E8605202CBEBCBC2C82 + 8383F53265707050ACADADB161D40D881113F7D7D7D75F07901E9061409141AE + C338DC89F28A0E87D0A5A5A529109393932C44E901BCFB2900B2002130040081 + C2088C63319453C4530E9471BC8381546B6F6FFF10809EBDF482CF0002030305 + 1A15EE115B7433C4523D90BE3939397C0F4394624AFABD06D8D8D8600FE801A0 + 1463113A98700B45DC2500F21E5E4075EBE9E9E1EB7B83CE152C60ACD9DADA2A + 8E8E8EBE0680662401D090D013E00DB5B0D088A4BA0D0DFC0EF501030DE739DD + 18408A510D40C7B297760C0018453341083C3D3D051DD178413B3B3B313F3FCF + 4DE4A381142E2A2A629D2C2C2C30C47B1E407B46257C73208107A406301182E4 + E4646ECFB3B3B39F0260E4E5E5B1F720508441ED017508004047B243BD23995A + 84302EBB627979B9989999114343439F024446468AD4D454365C5353C3460120 + 21641600808EE84F00F9F9F90CE0EAEAAAE701D9942A2B2BF9348B02F3D9C0EF + ABAAAAB8E1F4F5F5293D00531624D40E00B4B4B43C01E4E6E6B206D0EDD4BB97 + 15302E2E8EC5B7B2B2F22980AC0908973C0B604A4FE08A8645DFFDDDD6D6F69D + 01B2B2B2BE11C01FD4EF35EA562CA7B187520CD98AD5FD40C250463D5028FEA4 + 7A72CB00546235B4E35F290401A09667007523FA993F261240FDEF08058E0EA6 + 13B4C97FE960A263003A649850A7FA4622FC853CA125A38A3575173466C0A01A + E6F9D9239D1397E850D249E23EA386F50440954F432EB1A47B7BD2803DD1E164 + 699CCFBFE0188AFF0F0AC5396DE69CDAF3ADDE7F432A4026746F8ACD1A1B6F23 + 43F340000F246A1D3EFF0712759FFDF03F71A80000000049454E44AE426082} + end + object imgFSXStateConnected: TImage + Left = 4 + Top = 4 + Width = 32 + Height = 32 + AutoSize = True + Picture.Data = { + 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000200000 + 00200806000000737A7AF4000000097048597300000EC400000EC401952B0E1B + 000008124944415478DAC5977B7054D51DC7BFF7B177DF9BCDE649221188840D + 2492A243487C2C3E68476D29D599A2A53E3AAD5275A60FFFE91FDAD67F9CB68E + 33AD558A05E91F75C4222DA2B49476B4DD100832986002C612A76D90B0E4BDBB + D9DDECEE7DF677CEEE86242E26FCD53373F7DEB97BEEF97DCEF7F738E708F83F + 3761FE8B8AF61DCD922405645986284AD4837511F37761EE975691D1F83BFAB1 + ACFCDD84699A300C9D2EF3D2E8F1C7078A0254DDBC73693A36F2A6ECAFDB20D8 + 7D648FFDC500A4FC5D2CC67B8596330C187437F8DD2220331DB5AC44E4AFAE40 + CDB648C7A3B13900BEA6674F3856843634D59761ED32179C6E0982457F8B22D9 + 16721D8BCDBA88A656BE8F65E4402CC1443A65A277701A670646A00F75BD19ED + FDE9D61980D2F52F360B8AB7AFF9861B70FFCD15106DB9893315662BCFC558CC + FCF5DC37C40D9743443A6342CD92021AF087E363E8EDEA3414C5563E7A6C7B2C + 07D0FA7248F45487BFFAC5B568A1D98B9200E67E360087B06685C16200A8BF4C + 1F6F5AEF476DB9827852C7BEF7C691552DF49E4FE1E0A11390856C70F4E863E7 + F890810D3B09A02AFC350268AC7390EA050001DCF382B068EFB3E650047CA9B5 + 04D565F63C90855D6F8F209535F1F1A7691CF8731714510B0E87BF9D0768FB6D + 48745785EFDAD88CE6650E2E9BCF2B2291A6E8B5989A020B8585FD4FCD6517F1 + E5363FCA4B6C33EF7A0692F86777028669E1CC600687FFCE007402F856016037 + 77C11DB7AC41FB6A27B6DE164065A90D9A6E612CA66124AA632CAE2132AE619A + 6434CDE2243E9784CDED2528F55E367E8E667CA82B46C6019DBEEBFB4F16EFBE + F73E6C821A1CE978A400F02A01D4846F696BC44377FA70FB3A1FBC349830CFE9 + CC7046353134AA6272CAC0D0988AF1B8C1DFF93D12EE0DF9E173CB33FD2F4DA8 + 78E31F516435529226A3190C4045E7D15390580C743C9403286DDB13927CB5E1 + D61B1BD07CAD02874D44439D82B5F52EACBAC601492A1E013CB7498CA9940E85 + BE713B2EA7C96854C5BE700C094A3F9DFAE99A059520CE7EAAE2FD63DD145B99 + E058C7837980F6DF85244F6D78DD1756A2E95A1B6CB2489700990C7B9D228138 + D052EF44C0277F469562EDBF97B278AB33CE83CE20E306D5225537B94BCF0EAA + E839D9531CA0B979255613804286D9251388C4B281152232BCAC4AC6F52B1C04 + E4BC22C8274359ECEB8843D7993ACCB8C59F5523A740FF7915677A7A28B5B3C1 + F18E6FCE06B8261C0CD623B89400949C028A4DE0F92C3100F1722AAE5B69C7E6 + 365F51884F2E66F1DABB711E2F2CEA59E06979F9558A958F2F6838D7779AFC97 + 094ECC075876DD0A342E5560B793719A3D73814D2A005CAEDB32A9F2E4E600AA + 03B6CF004CA50CFCEC8D711EF50C4033732AB040CCE60106FB3FA4882680A3F3 + 00BC55CB515B21A3DA2FA1C42DC24E81C514281426BE3CD1F38377F8D0B4DC71 + C5C07C6EEF3826E239FFEBCC38F93F963431126399A32139D4474B442638391F + C053B91C5E5A845831712B123C2E114E52C34EAEB0B18B201EBED38BD6602E06 + D8E01D7D296CBCDE3D27535EF94B0C5D1F65C8B085695A079254D05234FB74C6 + E00AA52E2E02C045000EBACBF96C60F7FBDA5DF8FAAD1E6E9C49FAAB8371F4FC + 5BC54D8D763CF99512AE0E6BFB3B93D81B4E72E935EAC7EAC422006AC39E8A15 + F07A8A03DCBED6C18D30E3A9B481170EC4D07F419F99F5E3777B4909177FFEE8 + 7C16CFFC3EFA3900BDB4541340E74C1A5221721340657D518050B3033FBCD7CF + 17A768C2C0CFF74731386ACCF13DAB1F3F7E20C09FD394FFDB9E1FE52E2A0A30 + F42129909D05C02A21956277E57578FA1B65A8ABB471BFB320B451656DA142C4 + 54189ED4F18B3FC61099348A06E0AFB797A1AA54E681F8BD9DE3383FA253F199 + 0540CA4D4D13C085D3D48701144AF186DD548A6BC2AE8A9578645309EEBFCD47 + 25D8CEA3BFD08627353CFB7A0C1309F38A1570DB4637B6B47BF8F3CBEFC4F0B7 + EE3434329CD17200D304902080E9A16E52400D4E1E7BB800B02B2479ABC38EF2 + 551484325F8ED7D4D9716B93135B6EF2A08C4AF0E15329EC3E92F8DC125C1B90 + F0D213E53C4E8E7C90C24B6FC7732EA04294CA92617241825C908E7C4031A006 + A3C7F3CBB1BFF595904C1B12A5BC91528F0148947E221C548C1EBDDB87074891 + 432793D87D38B1E0BEF485EF04D040EA9DBB90C5F77F33CE6B000348AB24FD34 + 2991D6908D9C2205B460B42BBF21F1AFDF41315019B60556C3E950E0A6FCB753 + 39E6AB62AD823D4F55E160570ABB0EC7B150DBD2EEC6F67BFCDCF7773D13E101 + 99290422D5836C3A03F51201587A307A22BF25F3DFF822015485E5400364C53D + A3002B3EAC24EF7F7A098EF7A7B1F3D0C2003E82FFD34F96E0C0B1247EF9568C + AF822A03C8B2A24467032D0575F8347741ECE41339005FCBF321D9571D964B96 + 43905D906C0E329E5F9629FAB76EF4F2F40B9FC92C08C0DA77EFF161CF91295E + 01355A14748D4168B42BCED08E39096D7C809E93ABE2DD4F0D70006FE38F1A24 + F7927F89AE0A41A03DB9202AFC62FB7341620B4EFE847435DBE2FCC1C4A21311 + 4C95CD985E6BFC6E242EAAA63A5596E87F2EC94774D6DC2742741DB1F91B3699 + 6A32B7CC8A32372C08855391781507A3C2B18C00D8E9888E6630753AA0581015 + 0FD491D3AF0A8AE3B1F4D05E8B0F69F3B448E6F4708354B26687E45C12A2A54F + E40315CE857C672C2ED27A9E814E43B95DB48999EDB4A9EB4672E81D3D71F607 + B26FD54535DE65720049AE132C7DCA473D6A04D1552B88F6001B220731F3931F + 7901CBF3552A9CD30416F89951CBCA4428D022A635999CD35D14A88A5826399E + 0E2D10AFE61CB27855F81656D0C8A5AA694DF092FA3F8CC8125D22B18C700000 + 000049454E44AE426082} + Visible = False + end + object lblFSX: TLabel + Left = 42 + Top = 4 + Width = 146 + Height = 19 + Caption = 'Flight Simulator X' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object lblFSXState: TLabel + Left = 42 + Top = 23 + Width = 70 + Height = 13 + Caption = 'Not connected' + end + end + object pnlG940: TPanel + AlignWithMargins = True + Left = 3 + Top = 3 + Width = 224 + Height = 40 + Align = alClient + BevelOuter = bvNone + TabOrder = 1 + object imgStateFound: TImage + Left = 4 + Top = 4 + Width = 32 + Height = 32 + AutoSize = True + Picture.Data = { + 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000200000 + 00200806000000737A7AF4000000097048597300000B1300000B1301009A9C18 + 00000A4F6943435050686F746F73686F70204943432070726F66696C65000078 + DA9D53675453E9163DF7DEF4424B8880944B6F5215082052428B801491262A21 + 09104A8821A1D91551C1114545041BC8A088038E8E808C15512C0C8A0AD807E4 + 21A28E83A3888ACAFBE17BA36BD6BCF7E6CDFEB5D73EE7ACF39DB3CF07C0080C + 9648335135800CA9421E11E083C7C4C6E1E42E40810A2470001008B3642173FD + 230100F87E3C3C2B22C007BE000178D30B0800C04D9BC0301C87FF0FEA42995C + 01808401C07491384B08801400407A8E42A600404601809D98265300A0040060 + CB6362E300502D0060277FE6D300809DF8997B01005B94211501A09100201365 + 884400683B00ACCF568A450058300014664BC43900D82D00304957664800B0B7 + 00C0CE100BB200080C00305188852900047B0060C8232378008499001446F257 + 3CF12BAE10E72A00007899B23CB9243945815B082D710757572E1E28CE49172B + 14366102619A402EC27999193281340FE0F3CC0000A0911511E083F3FD78CE0E + AECECE368EB60E5F2DEABF06FF226262E3FEE5CFAB70400000E1747ED1FE2C2F + B31A803B06806DFEA225EE04685E0BA075F78B66B20F40B500A0E9DA57F370F8 + 7E3C3C45A190B9D9D9E5E4E4D84AC4425B61CA577DFE67C25FC057FD6CF97E3C + FCF7F5E0BEE22481325D814704F8E0C2CCF44CA51CCF92098462DCE68F47FCB7 + 0BFFFC1DD322C44962B9582A14E35112718E449A8CF332A52289429229C525D2 + FF64E2DF2CFB033EDF3500B06A3E017B912DA85D6303F64B27105874C0E2F700 + 00F2BB6FC1D4280803806883E1CF77FFEF3FFD47A02500806649927100005E44 + 242E54CAB33FC708000044A0812AB0411BF4C1182CC0061CC105DCC10BFC6036 + 844224C4C24210420A64801C726029AC82422886CDB01D2A602FD4401D34C051 + 688693700E2EC255B80E3D700FFA61089EC128BC81090441C808136121DA8801 + 628A58238E08179985F821C14804128B2420C9881451224B91354831528A5420 + 55481DF23D720239875C46BA913BC8003282FC86BC47319481B2513DD40CB543 + B9A8371A8446A20BD06474319A8F16A09BD072B41A3D8C36A1E7D0AB680FDA8F + 3E43C730C0E8180733C46C302EC6C342B1382C099363CBB122AC0CABC61AB056 + AC03BB89F563CFB17704128145C0093604774220611E4148584C584ED848A820 + 1C243411DA093709038451C2272293A84BB426BA11F9C4186232318758482C23 + D6128F132F107B8843C437241289433227B9900249B1A454D212D246D26E5223 + E92CA99B34481A2393C9DA646BB20739942C202BC885E49DE4C3E433E41BE421 + F25B0A9D624071A4F853E22852CA6A4A19E510E534E5066598324155A39A52DD + A8A15411358F5A42ADA1B652AF5187A81334759A39CD8316494BA5ADA295D31A + 681768F769AFE874BA11DD951E4E97D057D2CBE947E897E803F4770C0D861583 + C7886728199B18071867197718AF984CA619D38B19C754303731EB98E7990F99 + 6F55582AB62A7C1591CA0A954A9526951B2A2F54A9AAA6AADEAA0B55F355CB54 + 8FA95E537DAE46553353E3A909D496AB55AA9D50EB531B5367A93BA887AA67A8 + 6F543FA47E59FD890659C34CC34F43A451A0B15FE3BCC6200B6319B3782C216B + 0DAB86758135C426B1CDD97C762ABB98FD1DBB8B3DAAA9A13943334A3357B352 + F394663F07E39871F89C744E09E728A797F37E8ADE14EF29E2291BA6344CB931 + 655C6BAA96979658AB48AB51AB47EBBD36AEEDA79DA6BD45BB59FB810E41C74A + 275C2747678FCE059DE753D953DDA70AA7164D3D3AF5AE2EAA6BA51BA1BB4477 + BF6EA7EE989EBE5E809E4C6FA7DE79BDE7FA1C7D2FFD54FD6DFAA7F5470C5806 + B30C2406DB0CCE183CC535716F3C1D2FC7DBF151435DC34043A561956197E184 + 91B9D13CA3D5468D460F8C69C65CE324E36DC66DC6A326062621264B4DEA4DEE + 9A524DB9A629A63B4C3B4CC7CDCCCDA2CDD699359B3D31D732E79BE79BD79BDF + B7605A785A2CB6A8B6B86549B2E45AA659EEB6BC6E855A3959A558555A5DB346 + AD9DAD25D6BBADBBA711A7B94E934EAB9ED667C3B0F1B6C9B6A9B719B0E5D806 + DBAEB66DB67D6167621767B7C5AEC3EE93BD937DBA7D8DFD3D070D87D90EAB1D + 5A1D7E73B472143A563ADE9ACE9CEE3F7DC5F496E92F6758CF10CFD833E3B613 + CB29C4699D539BD347671767B97383F3888B894B82CB2E973E2E9B1BC6DDC8BD + E44A74F5715DE17AD2F59D9BB39BC2EDA8DBAFEE36EE69EE87DC9FCC349F299E + 593373D0C3C843E051E5D13F0B9F95306BDFAC7E4F434F8167B5E7232F632F91 + 57ADD7B0B7A577AAF761EF173EF63E729FE33EE33C37DE32DE595FCC37C0B7C8 + B7CB4FC36F9E5F85DF437F23FF64FF7AFFD100A78025016703898141815B02FB + F87A7C21BF8E3F3ADB65F6B2D9ED418CA0B94115418F82AD82E5C1AD2168C8EC + 90AD21F7E798CE91CE690E85507EE8D6D00761E6618BC37E0C2785878557863F + 8E7088581AD131973577D1DC4373DF44FA449644DE9B67314F39AF2D4A352A3E + AA2E6A3CDA37BA34BA3FC62E6659CCD5589D58496C4B1C392E2AAE366E6CBEDF + FCEDF387E29DE20BE37B17982FC85D7079A1CEC2F485A716A92E122C3A96404C + 884E3894F041102AA8168C25F21377258E0A79C21DC267222FD136D188D8435C + 2A1E4EF2482A4D7A92EC91BC357924C533A52CE5B98427A990BC4C0D4CDD9B3A + 9E169A76206D323D3ABD31839291907142AA214D93B667EA67E66676CBAC6585 + B2FEC56E8BB72F1E9507C96BB390AC05592D0AB642A6E8545A28D72A07B26765 + 5766BFCD89CA3996AB9E2BCDEDCCB3CADB90379CEF9FFFED12C212E192B6A586 + 4B572D1D58E6BDAC6A39B23C7179DB0AE315052B865606AC3CB88AB62A6DD54F + ABED5797AE7EBD267A4D6B815EC1CA82C1B5016BEB0B550AE5857DEBDCD7ED5D + 4F582F59DFB561FA869D1B3E15898AAE14DB1797157FD828DC78E51B876FCABF + 99DC94B4A9ABC4B964CF66D266E9E6DE2D9E5B0E96AA97E6970E6E0DD9DAB40D + DF56B4EDF5F645DB2F97CD28DBBB83B643B9A3BF3CB8BC65A7C9CECD3B3F54A4 + 54F454FA5436EED2DDB561D7F86ED1EE1B7BBCF634ECD5DB5BBCF7FD3EC9BEDB + 5501554DD566D565FB49FBB3F73FAE89AAE9F896FB6D5DAD4E6D71EDC703D203 + FD07230EB6D7B9D4D51DD23D54528FD62BEB470EC71FBEFE9DEF772D0D360D55 + 8D9CC6E223704479E4E9F709DFF71E0D3ADA768C7BACE107D31F761D671D2F6A + 429AF29A469B539AFB5B625BBA4FCC3ED1D6EADE7AFC47DB1F0F9C343C59794A + F354C969DAE982D39367F2CF8C9D959D7D7E2EF9DC60DBA2B67BE763CEDF6A0F + 6FEFBA1074E1D245FF8BE73BBC3BCE5CF2B874F2B2DBE51357B8579AAF3A5F6D + EA74EA3CFE93D34FC7BB9CBB9AAEB95C6BB9EE7ABDB57B66F7E91B9E37CEDDF4 + BD79F116FFD6D59E393DDDBDF37A6FF7C5F7F5DF16DD7E7227FDCECBBBD97727 + EEADBC4FBC5FF440ED41D943DD87D53F5BFEDCD8EFDC7F6AC077A0F3D1DC47F7 + 068583CFFE91F58F0F43058F998FCB860D86EB9E383E3939E23F72FDE9FCA743 + CF64CF269E17FEA2FECBAE17162F7EF8D5EBD7CED198D1A197F29793BF6D7CA5 + FDEAC0EB19AFDBC6C2C61EBEC97833315EF456FBEDC177DC771DEFA3DF0F4FE4 + 7C207F28FF68F9B1F553D0A7FB93199393FF040398F3FC63332DDB000009C549 + 44415478DA9D570B5094D7153EFB7EF0581658965704410444855D23D030680D + AD428805634631C6689B264ECD74DA344D26D3C9A4292A1AED34613A7D68673A + 93345640DB8ACA43790A62001D231A890875C37B9FB00FD85DF6D9732E6068C5 + 84E69FF9877F97FFDEF3DD73BEEF3B67397EBF5F06002EF816977FF6C207FCC0 + 01E072B91C9FCF37FBDDDCC5E170E8A66BB12D841C7C598C0FCE2507F5F961C6 + 3D03629118EC763B381C0E70D81DE072B920441E42A0606A6A8AFDE5F3F92010 + 08402E978350285C6C3B310190E0836329C1BD5E2FDBD86C3607F5F6F6FEC462 + B6C4210091D566155A262DD3098909D78D46E37AAD56CB9B99992150BE808000 + 6FEE86DCBE8D1B37FE01B3E3C52C018FC79BDF52B2240014D4E3F13C4CDBFB47 + DF3FD570B9E179A140083EBF0FA6A7A76164640456AE5C097A9D1E2626275876 + 444211A8D7A961F5EAD570E0A7AF6585CAE5DDB4974C2683B9927C33005A305F + 5342DFD6D6B6FDF59FBD7E363030909D846ECD971AC60379881CEC0E3B389D4E + 569A0D1B363070898989B07BCFEE8308E45DB7DB0D4141414B0380A966279FE7 + 8F542A858AD395C72B4F57FE325C11CEEA4BE0EA2FD7436A4A2A848785B3FAD3 + E969DD9A356BE0CEE7774095A1F2BDF3EE3B7961A1A1AD7494057C581C00D60A + 060606C03C39C94EFDF06D0470BEFAFCC74D8D4D7B140A050344EFD6D6D55210 + 080D0D6527260074D2F4F47470CE38619D7A9D6573FEE60C2CD997A496A8A8A8 + AFCF002D6E6C68983BFD57F2914824505B5BF7AFABED578BC3C3C31900AFCF0B + 1DD73A20636D06FB3CAF0C7AA6F531B131F0E65B6FDE5F959696C1998BB360CF + C50190A42A4E57B05A2ECC80582C063C7D437777F7F7C2C2C2182002DBFB452F + A4A4A4009FC767C14901243D2AC7C4C4041C3B7EEC4F45C545AFC1AC632CBC16 + 07401B9C3C7102377B0400A7B1A1B1FDC68D1B39048000D12989842B56AC60DB + D35ABA294309090960301A61FFFE575F2E78A6E0AFC3C3C30C2071E36B0178F0 + 5475F5F5E0240094AEB99449F1C435B5351FFDE3EC3F5FC4DA734522113324AD + 410BB1D1B12CB51EB7075C6E17E343F2CA6410880490979757A052A9EAABABAB + 19A81776BFF0CD3E407AD66834CCCD18A8391F085728A4D7AE5E5523BBD7F4DF + EFDFD9D2DABAD1ED71832C58F690C06E2CA15C1E0ACB972FA74C18DFFED5DB45 + 28D7818ABF574CA8D42A4FF1B6E279337A3C00EDB81686068700DD0D7A6EF580 + 3252C96A2A100A20521909C343C311353535151F967FB889C7E7416C4C2C8C8C + 8E62C03058BB7A2D984C266BF677B21B91271359D959EDE81B3DB77B6E8F2237 + 2C3B4B76BA29068278BC0AEA6AEB41815A4F4A4E82D6E656C8CECE86F1F17198 + 9A9E02A53272737767E789F3E7CFC7373537B3D344474503DA30E4E4E4C08E9D + 3B08A00BDFB7194D46C7B265CBDA51B6F7925352EA313BD7456211646565F983 + 838317073089FA3FF5C9697C29139627C443DB9536C8CCCC648CBEDFDFFFF33B + 3D3D472E5CB820B6DA6C603018180065849249502215435C5C3CE3008FCB636B + F47A3D58AD36282ADE3651B26B47AEE6C183DEA7729E82E8E8E84701500DC7C6 + C6A0E6621DA8D52A06A0E36A07F9B9E4E6CD9BBFC5BA1FB872A515B45A3DD639 + 84F9BE6BC60564CD1488144046430A898989C1F7B4C82321AC7BF249DC771CB6 + 3F5FFCD6F6E7B61D2762A3C21E05A0D3E9989B353634013217525293A1A9A929 + AEE7B39E13B76FDFDE4206333434C42C988212E3490944565AC7E57001BB2393 + 266545817766661666C00AED6DEDF04C61FEA9575EF9F18BC4AD4748481A45A2 + 40547414D4D75D6275777A3D595567CE7E14201024BB313BF7BEB8079A7FF723 + D3DD730D8A03429463705030604D190FFA07FA41887D4219198D010BC186C1BB + BBAF83C56281F8F8B85B9F9CFA58856D9A7AC97F03C01483C968825569AB3003 + CDB8B1748FAEB9A63CD2E7941B57A9818BF380116B3A643083CBE98269EC7CE6 + 491398903333F8ECC2F493F78B44124C7F2C6CC9DF02668B15BA3A3B1F3A247A + 87FD771F1CDF849ED19D9B9BFBD540423AAF3E57CD1C2C353515FE7CF28FBFFE + FCC167EFE561870B98E2825EA104B5A91F46BC4268E109C0E21A04B9301A7C76 + 1198274C3089249B0DE284106CCB79DFCFC393DBE0DAB54EF61D05273BE360C6 + F47AAD75DFDEBD1F1C2A3B7CF021007AA1E6620D2E0E096969BC523E6CBCF392 + 275407324E1280800F2EEF08240B5240333E090EB1199CBC29F05815F0DD2917 + 4458F4501310099A491B08D1BA373DBD114821D73A3EC57E3283B276314BF7E3 + F0A2D58D3362BEF1C62F4E1E3B76EC000380A396834CA6B9A939AEB6A6EE6F23 + 23A3B951514A6432F56D1EF0653670FB1DE0B7CBC01EA4C1FAFB20DABA02126D + 3AE0E13B7D76005B5018F3FDF5EBD781C3E940C275B066469E32DBFFFD30AE1D + 431E98A1A4A4E46CE9C1D21F29C2154E9A62258383830E742C41E97BA52DCDCD + CD39D4EB674925C3B62A41B2883009B809D78B785C30E57081C2CF851CAF1EFA + 3181B778A13085C155EA74B46C37FA463B7A8283D937C9917A84C1A0071BAA03 + BBE299D2D2DFEC93C982ED229158C2C1BAF1D15223AE775DFFFDC58B179F1B78 + 30C0BC801A4D803480313B147D9D8C854629A9241019CE071FC70F765402FE01 + 271AD0EA356934BF416B6B1B3B39A9C0472A4159EA745AB0A3833EBBB5F0CCC1 + C387F622204708CE85CC8AD1F514BB76EE6AE9EBEB4B9BB64F831E9152BDA412 + 2926CDCF864E1AC5E93BD23B699F864AEAF7418134DB71616D463A8825626869 + 6A65FD22292991AD19191D63B29C98304251D10FAA0E971DDE477C1361568270 + 1FE60338F729F7BFBABF4AA7D5255BAC16331A8B0F3921460B8DC5FF09580D45 + 421020F389C6349A538628AD64C14FC43EC18CC66030B11932252589B88E9D74 + 900537998C74F2AA2347CAF6E1791C344191D2E6E60C2C81DD211A1C1AF44644 + 44C8912CD3B8A91F83F2CF9D3B977CF7EE5D3576B59CEEAE6E954EAF4BC26C89 + 695AA2F290F3111802B12A358DD9AD103BE5F2F87874CA6130A15F188C06282C + 2CA83A7A148303C741F3852C440664420FE7012221DE0EB2548958C2BE259BAD + ACAC64A9A61B1B8E082D38A9ABAB8B0065B7B7B7AB474747D330DD812CE5892B + 90230ADC5C0E5E2421353322DD96822D678E1E3D82C1FD76370E2A544202B0E8 + 4C480E26E00B586AE864980136F3CD8F5D3482D3C2B8B838282F2FE7171414C4 + 5FBA74291D3DFE6994EF4B9111518162EC131ACD005B9F9F9F5F5576A4EC87B8 + 9F9DCC27501AC84AB9A499707E26989F6029232855068ED2D7D383038A520997 + 2F5F86AD5BB7C2D8E8584ED9E1B2BFE0049532E39AF1173E5B5879E8D0A19711 + BC833C804AF6984BF27FFF385DD8B6890F6C644380F7FAFA649D9F76AAB83CAE + A7A46457874824F42FF80DF8B88BF5826FFDF3FC7F2EAC94C73DA70E3A327709 + 6B84FF0180E96622F4B22C130000000049454E44AE426082} + Visible = False + end + object imgStateNotFound: TImage + Left = 4 + Top = 4 + Width = 32 + Height = 32 + AutoSize = True + Picture.Data = { + 0954506E67496D61676589504E470D0A1A0A0000000D49484452000000200000 + 00200806000000737A7AF4000000097048597300000B1300000B1301009A9C18 + 00000A4F6943435050686F746F73686F70204943432070726F66696C65000078 + DA9D53675453E9163DF7DEF4424B8880944B6F5215082052428B801491262A21 + 09104A8821A1D91551C1114545041BC8A088038E8E808C15512C0C8A0AD807E4 + 21A28E83A3888ACAFBE17BA36BD6BCF7E6CDFEB5D73EE7ACF39DB3CF07C0080C + 9648335135800CA9421E11E083C7C4C6E1E42E40810A2470001008B3642173FD + 230100F87E3C3C2B22C007BE000178D30B0800C04D9BC0301C87FF0FEA42995C + 01808401C07491384B08801400407A8E42A600404601809D98265300A0040060 + CB6362E300502D0060277FE6D300809DF8997B01005B94211501A09100201365 + 884400683B00ACCF568A450058300014664BC43900D82D00304957664800B0B7 + 00C0CE100BB200080C00305188852900047B0060C8232378008499001446F257 + 3CF12BAE10E72A00007899B23CB9243945815B082D710757572E1E28CE49172B + 14366102619A402EC27999193281340FE0F3CC0000A0911511E083F3FD78CE0E + AECECE368EB60E5F2DEABF06FF226262E3FEE5CFAB70400000E1747ED1FE2C2F + B31A803B06806DFEA225EE04685E0BA075F78B66B20F40B500A0E9DA57F370F8 + 7E3C3C45A190B9D9D9E5E4E4D84AC4425B61CA577DFE67C25FC057FD6CF97E3C + FCF7F5E0BEE22481325D814704F8E0C2CCF44CA51CCF92098462DCE68F47FCB7 + 0BFFFC1DD322C44962B9582A14E35112718E449A8CF332A52289429229C525D2 + FF64E2DF2CFB033EDF3500B06A3E017B912DA85D6303F64B27105874C0E2F700 + 00F2BB6FC1D4280803806883E1CF77FFEF3FFD47A02500806649927100005E44 + 242E54CAB33FC708000044A0812AB0411BF4C1182CC0061CC105DCC10BFC6036 + 844224C4C24210420A64801C726029AC82422886CDB01D2A602FD4401D34C051 + 688693700E2EC255B80E3D700FFA61089EC128BC81090441C808136121DA8801 + 628A58238E08179985F821C14804128B2420C9881451224B91354831528A5420 + 55481DF23D720239875C46BA913BC8003282FC86BC47319481B2513DD40CB543 + B9A8371A8446A20BD06474319A8F16A09BD072B41A3D8C36A1E7D0AB680FDA8F + 3E43C730C0E8180733C46C302EC6C342B1382C099363CBB122AC0CABC61AB056 + AC03BB89F563CFB17704128145C0093604774220611E4148584C584ED848A820 + 1C243411DA093709038451C2272293A84BB426BA11F9C4186232318758482C23 + D6128F132F107B8843C437241289433227B9900249B1A454D212D246D26E5223 + E92CA99B34481A2393C9DA646BB20739942C202BC885E49DE4C3E433E41BE421 + F25B0A9D624071A4F853E22852CA6A4A19E510E534E5066598324155A39A52DD + A8A15411358F5A42ADA1B652AF5187A81334759A39CD8316494BA5ADA295D31A + 681768F769AFE874BA11DD951E4E97D057D2CBE947E897E803F4770C0D861583 + C7886728199B18071867197718AF984CA619D38B19C754303731EB98E7990F99 + 6F55582AB62A7C1591CA0A954A9526951B2A2F54A9AAA6AADEAA0B55F355CB54 + 8FA95E537DAE46553353E3A909D496AB55AA9D50EB531B5367A93BA887AA67A8 + 6F543FA47E59FD890659C34CC34F43A451A0B15FE3BCC6200B6319B3782C216B + 0DAB86758135C426B1CDD97C762ABB98FD1DBB8B3DAAA9A13943334A3357B352 + F394663F07E39871F89C744E09E728A797F37E8ADE14EF29E2291BA6344CB931 + 655C6BAA96979658AB48AB51AB47EBBD36AEEDA79DA6BD45BB59FB810E41C74A + 275C2747678FCE059DE753D953DDA70AA7164D3D3AF5AE2EAA6BA51BA1BB4477 + BF6EA7EE989EBE5E809E4C6FA7DE79BDE7FA1C7D2FFD54FD6DFAA7F5470C5806 + B30C2406DB0CCE183CC535716F3C1D2FC7DBF151435DC34043A561956197E184 + 91B9D13CA3D5468D460F8C69C65CE324E36DC66DC6A326062621264B4DEA4DEE + 9A524DB9A629A63B4C3B4CC7CDCCCDA2CDD699359B3D31D732E79BE79BD79BDF + B7605A785A2CB6A8B6B86549B2E45AA659EEB6BC6E855A3959A558555A5DB346 + AD9DAD25D6BBADBBA711A7B94E934EAB9ED667C3B0F1B6C9B6A9B719B0E5D806 + DBAEB66DB67D6167621767B7C5AEC3EE93BD937DBA7D8DFD3D070D87D90EAB1D + 5A1D7E73B472143A563ADE9ACE9CEE3F7DC5F496E92F6758CF10CFD833E3B613 + CB29C4699D539BD347671767B97383F3888B894B82CB2E973E2E9B1BC6DDC8BD + E44A74F5715DE17AD2F59D9BB39BC2EDA8DBAFEE36EE69EE87DC9FCC349F299E + 593373D0C3C843E051E5D13F0B9F95306BDFAC7E4F434F8167B5E7232F632F91 + 57ADD7B0B7A577AAF761EF173EF63E729FE33EE33C37DE32DE595FCC37C0B7C8 + B7CB4FC36F9E5F85DF437F23FF64FF7AFFD100A78025016703898141815B02FB + F87A7C21BF8E3F3ADB65F6B2D9ED418CA0B94115418F82AD82E5C1AD2168C8EC + 90AD21F7E798CE91CE690E85507EE8D6D00761E6618BC37E0C2785878557863F + 8E7088581AD131973577D1DC4373DF44FA449644DE9B67314F39AF2D4A352A3E + AA2E6A3CDA37BA34BA3FC62E6659CCD5589D58496C4B1C392E2AAE366E6CBEDF + FCEDF387E29DE20BE37B17982FC85D7079A1CEC2F485A716A92E122C3A96404C + 884E3894F041102AA8168C25F21377258E0A79C21DC267222FD136D188D8435C + 2A1E4EF2482A4D7A92EC91BC357924C533A52CE5B98427A990BC4C0D4CDD9B3A + 9E169A76206D323D3ABD31839291907142AA214D93B667EA67E66676CBAC6585 + B2FEC56E8BB72F1E9507C96BB390AC05592D0AB642A6E8545A28D72A07B26765 + 5766BFCD89CA3996AB9E2BCDEDCCB3CADB90379CEF9FFFED12C212E192B6A586 + 4B572D1D58E6BDAC6A39B23C7179DB0AE315052B865606AC3CB88AB62A6DD54F + ABED5797AE7EBD267A4D6B815EC1CA82C1B5016BEB0B550AE5857DEBDCD7ED5D + 4F582F59DFB561FA869D1B3E15898AAE14DB1797157FD828DC78E51B876FCABF + 99DC94B4A9ABC4B964CF66D266E9E6DE2D9E5B0E96AA97E6970E6E0DD9DAB40D + DF56B4EDF5F645DB2F97CD28DBBB83B643B9A3BF3CB8BC65A7C9CECD3B3F54A4 + 54F454FA5436EED2DDB561D7F86ED1EE1B7BBCF634ECD5DB5BBCF7FD3EC9BEDB + 5501554DD566D565FB49FBB3F73FAE89AAE9F896FB6D5DAD4E6D71EDC703D203 + FD07230EB6D7B9D4D51DD23D54528FD62BEB470EC71FBEFE9DEF772D0D360D55 + 8D9CC6E223704479E4E9F709DFF71E0D3ADA768C7BACE107D31F761D671D2F6A + 429AF29A469B539AFB5B625BBA4FCC3ED1D6EADE7AFC47DB1F0F9C343C59794A + F354C969DAE982D39367F2CF8C9D959D7D7E2EF9DC60DBA2B67BE763CEDF6A0F + 6FEFBA1074E1D245FF8BE73BBC3BCE5CF2B874F2B2DBE51357B8579AAF3A5F6D + EA74EA3CFE93D34FC7BB9CBB9AAEB95C6BB9EE7ABDB57B66F7E91B9E37CEDDF4 + BD79F116FFD6D59E393DDDBDF37A6FF7C5F7F5DF16DD7E7227FDCECBBBD97727 + EEADBC4FBC5FF440ED41D943DD87D53F5BFEDCD8EFDC7F6AC077A0F3D1DC47F7 + 068583CFFE91F58F0F43058F998FCB860D86EB9E383E3939E23F72FDE9FCA743 + CF64CF269E17FEA2FECBAE17162F7EF8D5EBD7CED198D1A197F29793BF6D7CA5 + FDEAC0EB19AFDBC6C2C61EBEC97833315EF456FBEDC177DC771DEFA3DF0F4FE4 + 7C207F28FF68F9B1F553D0A7FB93199393FF040398F3FC63332DDB000007EF49 + 44415478DAA557494C5459147D9FFA50C52020833289322B4A83A248DA38A431 + 22A24C894A42ECD00BDDB831313171E1C279484C74D5B8E9EE841834B62DA0E0 + 80A222600403E21845914150041151849AFB9C177E350A38F54F7EA07EFDF7EE + B9E79E73EF2BC56EB71B841016F10317D63A6E455184939393B0D96CF2B376F1 + B9768F73A90A5E56BF070003984C26613018C4C78F1FC5D0D090FCCB6793274F + 96C13F7CF820FFAAAA2A9C9D9DE573171797FF0FC06AB5CA8DFBFBFBF50F1F3E + 5CF0EEDD3B2F00500706067478660A0F0FEFECEDED0DEEEEEE5686878709CAEE + EEEE6E5FB26449EFD2A54BEB00DE4E96743ADDF70160508BC5F18AEEE0C18339 + 972E5D5A80EC6CF8CE3E3838285EBC78618F8E8EB622B8FAF6ED5BC90AB34E4C + 4C749A33678E69F3E6CD053E3E3E9DDCCBCBCB4B2BC9D7016835E645F4555555 + B15BB66CC9F3F0F0B020133BB379FEFCB9FC1E542B086C67F62C0D3217040766 + F41B366CA800904AB3D92C264D9AF46D0040EBE8CC859B9B9B3871E2C40ADCBF + F8FBFB0FB3BE0477F1E2453173E64CE1E7E727EBCFECB92E2E2E4EDCBB774F99 + 3B77AE7DC78E1D7F818156EE334A0FE303A0D09E3E7D2A4825B31E0DA0A4A424 + FBCA952B490060E467BE5B5E5E2E10442080CC980098697C7CBC001B3A946130 + 3535B50080FB99796060E09719E0E28A8A0A99C568FBB8BABA32586E7575F54F + C8D6440014666D6DAD0CC6CF9A33F83FD7070707ABDBB66DEB9E3D7BF6EF5A9C + 517B8E0F80962A2A2A22FA4F18A0F590FDAF75757531BEBEBE660222583842CC + 9A354BAA9BC18D46A3B41ECBD1D7D7E772E8D0A19AACACAC724AEA9B6CC80D8E + 1D3B2637FB0C8072F9F2E5DF6EDFBE1D0900260262961461545494D403D7F2A6 + 1E203E015B3A6FDAB4E9EF55AB5635767474C83DA98D2F026056172E5C180380 + B4969595659F3E7D3A11B577D2EBF53680B0C07AA45A52CBB5BCA907D85281E0 + 949494944268A419FA91A0F2F2F2BEDE07B829336337E3A5B901E273AEA9A909 + 82BAFD9F3C791277EDDAB5307C67A3B7C900454900B4645858980E4C0C6CDFBE + BD08E5E943598700C4969D9DAD35A38901BC7AF54AB4B5B549C477EEDC110101 + 01B2A6B4D0D4A95305E874071B6B8F1C391201905632D0D9D929A907C5CA9B37 + 6F869393935BA09321FC6D43DFE86E6A6A1A0033C3EBD7AFB7C98EA6D34D5C82 + F3E7CFCBCD626262446565A5C026E2E5CB97D26600107EEBD6ADACD2D2523F88 + D2C46C828282586FB168D122B16EDD3A02B4E27D239E59A64F9FDE0EE67AB157 + 3304DE492D2D5CB8D0EEE9E9393E00FAFFF8F1E3022F09D028AE5FBF2E929292 + A868D1DCDC9C7CF7EEDD1567CF9ED5BF7FFFDEFCFAF56B492759A105E90C0494 + 1AC073056B14BC23E705A8EFCBCDCDFDE3D9B3673D040AD06301B0865D5D5D14 + 9B98376F9E0400DF0BB451B5A1A12115007E06201B4A64A5D5088AB605C58260 + E800361A3A84656129D931E7CF9FAF605F7D4E4E4E29EEDA91F13D1600C5479A + 6137D9DDD86241B317749081CC63E104537B7B3B67900CAA8D668A95EBE81A64 + 2B054C56A64C992299E433CC117D5A5A5AFDC68D1BFF898888182B42DA0E4291 + F5A40D597704083975EA540E8204A2EB191F3D7A245A5A5A64A6DA41037614A8 + A7BCA903B024C54AE1A6A7A7CBE0F5F5F502E3DB79C68C191D8585850518D364 + E65300A0586E80B62919C0C6F100948EE0EEB099995943DD92256DE2B104D40C + FF271B7C4E4021212102FD9F410504AB7548F60EE3E1C387FF04F0AEC58B17FF + 07803E2F2E2EA6CF655B2D28285876FFFEFD94D8D85805F44A80DC7864F6CBA0 + DEDEDEB26C04C1405A1BE6F3E5CB97CBCC6FDEBCE9E88EBC502227E8C2949F9F + 5FBD77EFDE2A0700BE70EEDC393610036C97D6DADABA00945BF0D9C6BA327068 + 68A8B422B3A4557951686486CFC90CB5B06CD9324770ED5DEEA1BD0700EE5BB7 + 6EADC28C28930018884D0662F3C6B4CB41438946FD8CC8D8AED559DB449BF5B4 + 19EB4C85B32CFC8EE583DA2513748E169CEF69C1799C83151B77EFDE5D02B62D + 0A6CA7A2E359E05FDDCE9D3BF3917D140F1B9AA8D8FFB981D692695306A08830 + 9024F5CC9625A16B18F0C68D1B0EA0740813A045D137F49999998DBB76ED3A83 + BDCDF84E55B09902CF7B60C4A6A304716812161E1E596F06270866CB9B47290D + 9036F934F750B8BCD8B4983999D1663F4B0386F5AB57AF6EDCB367CF193C3353 + 27B21503B91B28C97FFCF87118501B891474DAC1886D24633BB366B723287A9F + 83874D888078252424C80E78F5EA55392F2223232540CE06960525D267646434 + ECDBB7AF989D9EFB8CAC5515D4C403F37A2D50FA81CE61506887265428DB93F5 + C26727646CE50998070A7C27013133829A366D9A6CD30CC4332467072F88D811 + 1CBDA061FFFEFD67008AECCA193332E6559E6275EC6CE858060432F3A48BA03A + 58D2EFC1830701D82014E50984727D10C000AFEB780EE0042418BE0FAB4ACA79 + B375633F69CD9E9E1E47703A8DA522F5D48FE33C4011E2B6B08990465EA4EFE4 + C993926ADED84887DAFBA2A1040250084416047AFD41B72B6E15949BA1111B75 + 42116AC157AE5CD978E0C0817F189CCF493D018C7B26D484436A78D064532220 + EDD8C5EFB89093EEE8D1A34EE8E9DE388E07C00161B06F02FABE1B32B340C45C + EFC2E0CC1CFB99B98EDA19E7E7D9C40712ADD1703119E1E184E0481FE705070D + 7E1D89356BD6506CA1105826EAEE0F6728A0BD096A2F0178338332F309AEEFFF + 713A7A6CB36CEC0F04081719D0F902A1091B5CD5CE2636EA37E01701FCF0CFF3 + CF31F16C38E20E4656BE618DFA2FD3308F8CBFBD23FB0000000049454E44AE42 + 6082} + end + object lblG940Throttle: TLabel + Left = 42 + Top = 4 + Width = 114 + Height = 19 + Caption = 'G940 Throttle' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + end + object lblG940ThrottleState: TLabel + Left = 42 + Top = 23 + Width = 59 + Height = 13 + Caption = 'Searching...' + end end end end diff --git a/G940LEDControl/Forms/MainFrm.pas b/G940LEDControl/Forms/MainFrm.pas index e07ffc1..b5ddb43 100644 --- a/G940LEDControl/Forms/MainFrm.pas +++ b/G940LEDControl/Forms/MainFrm.pas @@ -2,15 +2,15 @@ unit MainFrm; interface uses - Classes, - Contnrs, - Controls, - ComCtrls, - ExtCtrls, - Forms, - Messages, - StdCtrls, - Windows, + System.Classes, + System.Contnrs, + Vcl.ComCtrls, + Vcl.Controls, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.StdCtrls, + Winapi.Messages, + Winapi.Windows, OtlComm, OtlEventMonitor, @@ -19,68 +19,47 @@ uses pngimage, X2UtPersistIntf, - LEDFunctionMap, + FSXSimConnectIntf, LEDStateConsumer, - LEDStateProvider; + Profile, + Settings; const CM_ASKAUTOUPDATE = WM_APP + 1; - MSG_UPDATE = 1; - MSG_NOUPDATE = 2; + TM_UPDATE = 1; + TM_NOUPDATE = 2; + TM_FSXSTATE = 3; + + LED_COUNT = 8; + + DBT_DEVICEARRIVAL = $8000; + DBT_DEVICEREMOVECOMPLETE = $8004; + DBT_DEVTYP_DEVICEINTERFACE = $0005; + DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = $0004; + type - TComboBoxArray = array[0..7] of TComboBoxEx; + TLEDControls = record + ConfigureButton: TButton; + CategoryLabel: TLabel; + FunctionLabel: TLabel; + end; + TMainForm = class(TForm) imgStateNotFound: TImage; lblG940Throttle: TLabel; imgStateFound: TImage; lblG940ThrottleState: TLabel; - btnRetry: TButton; - pcConnections: TPageControl; + PageControl: TPageControl; pnlG940: TPanel; - tsFSX: TTabSheet; - gbFSXButtons: TGroupBox; - lblFSXP1: TLabel; - cmbFSXP1: TComboBoxEx; - cmbFSXP2: TComboBoxEx; - lblFSXP2: TLabel; - cmbFSXP3: TComboBoxEx; - lblFSXP3: TLabel; - cmbFSXP4: TComboBoxEx; - lblFSXP4: TLabel; - cmbFSXP5: TComboBoxEx; - lblFSXP5: TLabel; - cmbFSXP6: TComboBoxEx; - lblFSXP6: TLabel; - cmbFSXP7: TComboBoxEx; - lblFSXP7: TLabel; - cmbFSXP8: TComboBoxEx; - lblFSXP8: TLabel; - gbFSXConnection: TGroupBox; - btnFSXConnect: TButton; - btnFSXDisconnect: TButton; - lblFSXLocal: TLabel; - pcFSXOptions: TPageControl; - tsFSXLEDButtons: TTabSheet; - tsFSXExtra: TTabSheet; - GroupBox1: TGroupBox; - cbFSXToggleZoom: TCheckBox; - lblFSXToggleZoomButton: TLabel; - lblFSXZoomDepressed: TLabel; - lblFSXZoomPressed: TLabel; - lblFSXToggleZoomButtonName: TLabel; - btnFSXToggleZoom: TButton; - cmbFSXZoomDepressed: TComboBox; - cmbFSXZoomPressed: TComboBox; - GroupBox2: TGroupBox; tsAbout: TTabSheet; lblVersionCaption: TLabel; lblVersion: TLabel; - Label1: TLabel; - Label2: TLabel; + lblProductName: TLabel; + lblCopyright: TLabel; lblWebsiteLink: TLinkLabel; lblEmailLink: TLinkLabel; lblWebsite: TLabel; @@ -88,43 +67,93 @@ type cbCheckUpdates: TCheckBox; btnCheckUpdates: TButton; lblProxy: TLabel; + tsFSX: TTabSheet; + btnP1: TButton; + lblP1Function: TLabel; + lblP1Category: TLabel; + btnP2: TButton; + lblP2Function: TLabel; + lblP2Category: TLabel; + btnP3: TButton; + lblP3Function: TLabel; + lblP3Category: TLabel; + btnP4: TButton; + lblP4Function: TLabel; + lblP4Category: TLabel; + btnP5: TButton; + lblP5Function: TLabel; + lblP5Category: TLabel; + btnP6: TButton; + lblP6Function: TLabel; + lblP6Category: TLabel; + btnP7: TButton; + lblP7Function: TLabel; + lblP7Category: TLabel; + btnP8: TButton; + lblP8Function: TLabel; + lblP8Category: TLabel; + lblProfile: TLabel; + cmbProfiles: TComboBox; + btnSaveProfile: TButton; + btnDeleteProfile: TButton; + bvlProfiles: TBevel; + pnlFSX: TPanel; + imgFSXStateNotConnected: TImage; + imgFSXStateConnected: TImage; + lblFSX: TLabel; + lblFSXState: TLabel; + pnlState: TPanel; procedure FormCreate(Sender: TObject); - procedure btnRetryClick(Sender: TObject); - procedure btnFSXConnectClick(Sender: TObject); - procedure btnFSXDisconnectClick(Sender: TObject); - procedure btnFSXToggleZoomClick(Sender: TObject); - procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); - procedure FunctionComboBoxChange(Sender: TObject); procedure lblLinkLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType); procedure btnCheckUpdatesClick(Sender: TObject); + procedure LEDButtonClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure cmbProfilesClick(Sender: TObject); + procedure cbCheckUpdatesClick(Sender: TObject); + procedure btnSaveProfileClick(Sender: TObject); + procedure btnDeleteProfileClick(Sender: TObject); private + FLEDControls: array[0..LED_COUNT - 1] of TLEDControls; FEventMonitor: TOmniEventMonitor; + + FProfilesFilename: string; + FProfiles: TProfileList; + FActiveProfile: TProfile; + FLockChangeProfile: Boolean; FStateConsumerTask: IOmniTaskControl; - FFSXComboBoxes: TComboBoxArray; - FFSXToggleZoomDeviceGUID: TGUID; - FFSXToggleZoomButtonIndex: Integer; + + FDeviceNotification: Pointer; + FG940Found: Boolean; + + FSettingsFileName: string; + FSettings: TSettings; + + procedure SetActiveProfile(const Value: TProfile); protected - procedure LoadFunctions(AProviderClass: TLEDStateProviderClass; AComboBoxes: TComboBoxArray); - procedure SetFunctions(AComboBoxes: TComboBoxArray); + procedure RegisterDeviceArrival; + procedure UnregisterDeviceArrival; - procedure ReadFunctions(AReader: IX2PersistReader; AComboBoxes: TComboBoxArray); - procedure ReadFSXExtra(AReader: IX2PersistReader); - procedure ReadAutoUpdate(AReader: IX2PersistReader); - procedure WriteFunctions(AWriter: IX2PersistWriter; AComboBoxes: TComboBoxArray); - procedure WriteFSXExtra(AWriter: IX2PersistWriter); - procedure WriteAutoUpdate(AWriter: IX2PersistWriter); + procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE; + protected + procedure FindLEDControls; + procedure LoadProfiles; + procedure SaveProfiles; - procedure LoadDefaultProfile; - procedure SaveDefaultProfile; + procedure LoadSettings; + procedure SaveSettings; + + function CreateDefaultProfile: TProfile; + procedure LoadActiveProfile; + procedure UpdateButton(AProfile: TProfile; AButtonIndex: Integer); + + procedure AddProfile(AProfile: TProfile); + procedure UpdateProfile(AProfile: TProfile); + procedure DeleteProfile(AProfile: TProfile; ASetActiveProfile: Boolean); procedure SetDeviceState(const AMessage: string; AFound: Boolean); - procedure SetFSXToggleZoomButton(const ADeviceGUID: TGUID; AButtonIndex: Integer; const ADisplayText: string); - - procedure InitializeStateProvider(AProviderClass: TLEDStateProviderClass); - procedure FinalizeStateProvider; - - procedure UpdateMapping; + procedure SetFSXState(const AMessage: string; AConnected: Boolean); +// procedure SetFSXToggleZoomButton(const ADeviceGUID: TGUID; AButtonIndex: Integer; const ADisplayText: string); procedure CheckForUpdatesThread(const ATask: IOmniTask); procedure CheckForUpdates(AReportNoUpdates: Boolean); @@ -132,64 +161,72 @@ type procedure EventMonitorMessage(const task: IOmniTaskControl; const msg: TOmniMessage); procedure EventMonitorTerminated(const task: IOmniTaskControl); - procedure HandleDeviceStateMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleRunInMainThreadMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleProviderKilled(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleProviderKilledFSX(ATask: IOmniTaskControl; AMessage: TOmniMessage); + procedure HandleDeviceStateMessage(AMessage: TOmniMessage); + procedure HandleFSXStateMessage(AMessage: TOmniMessage); procedure CMAskAutoUpdate(var Msg: TMessage); message CM_ASKAUTOUPDATE; + property ActiveProfile: TProfile read FActiveProfile write SetActiveProfile; property EventMonitor: TOmniEventMonitor read FEventMonitor; + property Profiles: TProfileList read FProfiles; + property Settings: TSettings read FSettings; property StateConsumerTask: IOmniTaskControl read FStateConsumerTask; end; implementation uses - ComObj, - Dialogs, - ShellAPI, - SysUtils, + System.SysUtils, + System.Win.ComObj, + Vcl.Dialogs, + Vcl.Graphics, + Winapi.ShellAPI, IdException, IdHTTP, OtlCommon, X2UtApp, - X2UtPersistRegistry, + X2UtPersistXML, - ButtonSelectFrm, - FSXLEDStateProvider, - G940LEDStateConsumer; + ButtonFunctionFrm, + ConfigConversion, + FSXSimConnectStateMonitor, + G940LEDStateConsumer, + LEDColorIntf, + LEDFunctionIntf, + LEDFunctionRegistry, + StaticResources; {$R *.dfm} const - SPECIAL_CATEGORY = -1; + DefaultProfileName = 'Default'; + ProfilePostfixModified = ' (modified)'; + + FilenameProfiles = 'G940LEDControl\Profiles.xml'; + FilenameSettings = 'G940LEDControl\Settings.xml'; + + TextStateSearching = 'Searching...'; + TextStateNotFound = 'Not found'; + TextStateFound = 'Connected'; + + TextFSXConnected = 'Connected'; + TextFSXDisconnected = 'Not connected'; + TextFSXFailed = 'Failed to connect'; - TEXT_STATE_SEARCHING = 'Searching...'; - TEXT_STATE_NOTFOUND = 'Not found'; - TEXT_STATE_FOUND = 'Connected'; - KEY_SETTINGS = '\Software\X2Software\G940LEDControl\'; - SECTION_DEFAULTPROFILE = 'DefaultProfile'; - SECTION_FSX = 'FSX'; - SECTION_SETTINGS = 'Settings'; type - TComboBoxFunctionConsumer = class(TInterfacedObject, IFunctionConsumer) - private - FComboBox: TComboBoxEx; + TFSXStateMonitorWorker = class(TOmniWorker, IFSXSimConnectStateObserver) protected - { IFunctionConsumer } - procedure SetCategory(const ACategory: string); - procedure AddFunction(AFunction: Integer; const ADescription: string); + function Initialize: Boolean; override; + procedure Cleanup; override; - property ComboBox: TComboBoxEx read FComboBox; - public - constructor Create(AComboBox: TComboBoxEx); + { IFSXSimConnectStateObserver } + procedure ObserverStateUpdate(ANewState: TFSXSimConnectState); end; @@ -198,45 +235,387 @@ type { TMainForm } procedure TMainForm.FormCreate(Sender: TObject); var - consumer: IOmniWorker; + worker: IOmniWorker; begin lblVersion.Caption := App.Version.FormatVersion(False); - pcConnections.ActivePageIndex := 0; - pcFSXOptions.ActivePageIndex := 0; - lblFSXToggleZoomButtonName.Caption := ''; + PageControl.ActivePageIndex := 0; FEventMonitor := TOmniEventMonitor.Create(Self); - consumer := TG940LEDStateConsumer.Create; - FStateConsumerTask := FEventMonitor.Monitor(CreateTask(consumer)).MsgWait; + worker := TG940LEDStateConsumer.Create; + FStateConsumerTask := EventMonitor.Monitor(CreateTask(worker)).MsgWait; EventMonitor.OnTaskMessage := EventMonitorMessage; EventMonitor.OnTaskTerminated := EventMonitorTerminated; StateConsumerTask.Run; - FFSXComboBoxes[0] := cmbFSXP1; - FFSXComboBoxes[1] := cmbFSXP2; - FFSXComboBoxes[2] := cmbFSXP3; - FFSXComboBoxes[3] := cmbFSXP4; - FFSXComboBoxes[4] := cmbFSXP5; - FFSXComboBoxes[5] := cmbFSXP6; - FFSXComboBoxes[6] := cmbFSXP7; - FFSXComboBoxes[7] := cmbFSXP8; - LoadFunctions(TFSXLEDStateProvider, FFSXComboBoxes); - LoadDefaultProfile; + worker := TFSXStateMonitorWorker.Create; + EventMonitor.Monitor(CreateTask(worker)).Run; + + FindLEDControls; + + FProfilesFilename := App.UserPath + FilenameProfiles; + FProfiles := TProfileList.Create(True); + LoadProfiles; + + FSettingsFileName := App.UserPath + FilenameSettings; + LoadSettings; + + RegisterDeviceArrival; end; -procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); +procedure TMainForm.FormDestroy(Sender: TObject); begin - if Assigned(StateConsumerTask) then - begin - SaveDefaultProfile; + UnregisterDeviceArrival; - LEDStateConsumer.Finalize(StateConsumerTask); - CanClose := False; + FreeAndNil(FProfiles); +end; + + +procedure TMainForm.RegisterDeviceArrival; +type + TDevBroadcastDeviceInterface = packed record + dbcc_size: DWORD; + dbcc_devicetype: DWORD; + dbcc_reserved: DWORD; + dbcc_classguid: TGUID; + dbcc_name: PChar; + end; + +var + request: TDevBroadcastDeviceInterface; + +begin + ZeroMemory(@request, SizeOf(request)); + request.dbcc_size := SizeOf(request); + request.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE; + + FDeviceNotification := RegisterDeviceNotification(Self.Handle, @request, + DEVICE_NOTIFY_WINDOW_HANDLE or + DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); +end; + + +procedure TMainForm.UnregisterDeviceArrival; +begin + if Assigned(FDeviceNotification) then + begin + UnregisterDeviceNotification(FDeviceNotification); + FDeviceNotification := nil; + end; +end; + + +procedure TMainForm.WMDeviceChange(var Msg: TMessage); +begin + if not Assigned(StateConsumerTask) then + exit; + + case Msg.WParam of + DBT_DEVICEARRIVAL: + if (not FG940Found) then + StateConsumerTask.Comm.Send(TM_FINDTHROTTLEDEVICE); + + DBT_DEVICEREMOVECOMPLETE: + if FG940Found then + StateConsumerTask.Comm.Send(TM_TESTTHROTTLEDEVICE); + end; +end; + + +procedure TMainForm.FindLEDControls; + + function ComponentByName(const AName: string; ATag: NativeInt): TComponent; + begin + Result := FindComponent(AName); + if not Assigned(Result) then + raise EArgumentException.CreateFmt('"%s" is not a valid component', [AName]); + + Result.Tag := ATag; + end; + +var + ledIndex: Integer; + ledNumber: string; + +begin + for ledIndex := 0 to Pred(LED_COUNT) do + begin + ledNumber := IntToStr(Succ(ledIndex)); + + FLEDControls[ledIndex].ConfigureButton := (ComponentByName('btnP' + ledNumber, ledIndex) as TButton); + FLEDControls[ledIndex].CategoryLabel := (ComponentByName('lblP' + ledNumber + 'Category', ledIndex) as TLabel); + FLEDControls[ledIndex].FunctionLabel := (ComponentByName('lblP' + ledNumber + 'Function', ledIndex) as TLabel); + + FLEDControls[ledIndex].ConfigureButton.OnClick := LEDButtonClick; + FLEDControls[ledIndex].CategoryLabel.Caption := ''; + FLEDControls[ledIndex].CategoryLabel.Font.Color := clGrayText; + FLEDControls[ledIndex].FunctionLabel.Caption := ''; + end; +end; + + +procedure TMainForm.LoadProfiles; +var + defaultProfile: TProfile; + persistXML: TX2UtPersistXML; + profile: TProfile; + +begin + if not FileExists(FProfilesFilename) then + begin + { Check if version 0.x settings are in the registry } + defaultProfile := ConfigConversion.ConvertProfile0To1; + + if not Assigned(defaultProfile) then + defaultProfile := CreateDefaultProfile + else + begin + defaultProfile.Name := DefaultProfileName; + defaultProfile.IsTemporary := True; + end; + + if Assigned(defaultProfile) then + Profiles.Add(defaultProfile); + end else + begin + persistXML := TX2UtPersistXML.Create; + try + persistXML.FileName := FProfilesFilename; + Profiles.Load(persistXML.CreateReader); + finally + FreeAndNil(persistXML); + end; + end; + + { Make sure we always have a profile } + if Profiles.Count = 0 then + Profiles.Add(CreateDefaultProfile); + + FLockChangeProfile := True; + try + cmbProfiles.Items.BeginUpdate; + try + cmbProfiles.Items.Clear; + + for profile in Profiles do + cmbProfiles.Items.AddObject(profile.Name, profile); + finally + cmbProfiles.Items.EndUpdate; + end; + finally + FLockChangeProfile := False; + end; +end; + + +procedure TMainForm.SaveProfiles; +var + persistXML: TX2UtPersistXML; + +begin + persistXML := TX2UtPersistXML.Create; + try + persistXML.FileName := FProfilesFilename; + Profiles.Save(persistXML.CreateWriter); + finally + FreeAndNil(persistXML); + end; +end; + + +procedure TMainForm.LoadSettings; +var + persistXML: TX2UtPersistXML; + profile: TProfile; + +begin + if not FileExists(FSettingsFileName) then + begin + { Check if version 0.x settings are in the registry } + FSettings := ConfigConversion.ConvertSettings0To1; + + if not Assigned(FSettings) then + FSettings := TSettings.Create; + end else + begin + FSettings := TSettings.Create; + + persistXML := TX2UtPersistXML.Create; + try + persistXML.FileName := FSettingsFileName; + Settings.Load(persistXML.CreateReader); + finally + FreeAndNil(persistXML); + end; + end; + + { Default profile } + profile := nil; + if Length(Settings.ActiveProfile) > 0 then + profile := Profiles.Find(Settings.ActiveProfile); + + { LoadProfiles ensures there's always at least 1 profile } + if (not Assigned(profile)) and (Profiles.Count > 0) then + profile := Profiles[0]; + + SetActiveProfile(profile); + + { Auto-update } + cbCheckUpdates.Checked := Settings.CheckUpdates; + + if not Settings.HasCheckUpdates then + PostMessage(Self.Handle, CM_ASKAUTOUPDATE, 0, 0) + else if Settings.CheckUpdates then + CheckForUpdates(False); +end; + + +procedure TMainForm.SaveSettings; +var + persistXML: TX2UtPersistXML; + +begin + persistXML := TX2UtPersistXML.Create; + try + persistXML.FileName := FSettingsFileName; + Settings.Save(persistXML.CreateWriter); + finally + FreeAndNil(persistXML); + end; +end; + + +function TMainForm.CreateDefaultProfile: TProfile; +begin + { Default button functions are assigned during UpdateButton } + Result := TProfile.Create; + Result.Name := DefaultProfileName; + Result.IsTemporary := True; +end; + + +procedure TMainForm.LoadActiveProfile; +var + buttonIndex: Integer; + +begin + if not Assigned(ActiveProfile) then + exit; + + for buttonIndex := 0 to Pred(LED_COUNT) do + UpdateButton(ActiveProfile, buttonIndex); + + if Assigned(StateConsumerTask) then + StateConsumerTask.Comm.Send(TM_LOADPROFILE, ActiveProfile); +end; + + +procedure TMainForm.UpdateButton(AProfile: TProfile; AButtonIndex: Integer); +var + button: TProfileButton; + providerUID: string; + functionUID: string; + provider: ILEDFunctionProvider; + buttonFunction: ILEDFunction; + +begin + if AProfile.HasButton(AButtonIndex) then + begin + button := AProfile.Buttons[AButtonIndex]; + providerUID := button.ProviderUID; + functionUID := button.FunctionUID; + end else + begin + providerUID := StaticProviderUID; + functionUID := StaticFunctionUID[lcGreen]; + end; + + buttonFunction := nil; + provider := TLEDFunctionRegistry.Find(providerUID); + if Assigned(provider) then + buttonFunction := provider.Find(functionUID); + + if Assigned(buttonFunction) then + begin + FLEDControls[AButtonIndex].CategoryLabel.Caption := buttonFunction.GetCategoryName; + FLEDControls[AButtonIndex].FunctionLabel.Caption := buttonFunction.GetDisplayName; + end; +end; + + +procedure TMainForm.AddProfile(AProfile: TProfile); +begin + Profiles.Add(AProfile); + cmbProfiles.Items.AddObject(AProfile.Name, AProfile); + SetActiveProfile(AProfile); +end; + + +procedure TMainForm.UpdateProfile(AProfile: TProfile); +var + itemIndex: Integer; + oldItemIndex: Integer; + +begin + itemIndex := cmbProfiles.Items.IndexOfObject(AProfile); + if itemIndex > -1 then + begin + oldItemIndex := cmbProfiles.ItemIndex; + FLockChangeProfile := True; + try + cmbProfiles.Items[itemIndex] := AProfile.Name; + cmbProfiles.ItemIndex := oldItemIndex; + finally + FLockChangeProfile := False; + end; + end; +end; + + +procedure TMainForm.DeleteProfile(AProfile: TProfile; ASetActiveProfile: Boolean); +var + itemIndex: Integer; + +begin + if AProfile = ActiveProfile then + FActiveProfile := nil; + + itemIndex := cmbProfiles.Items.IndexOfObject(AProfile); + if itemIndex > -1 then + begin + Profiles.Remove(AProfile); + cmbProfiles.Items.Delete(itemIndex); + + if Profiles.Count = 0 then + AddProfile(CreateDefaultProfile); + + if ASetActiveProfile then + begin + if itemIndex >= Profiles.Count then + itemIndex := Pred(Profiles.Count); + + FLockChangeProfile := True; + try + cmbProfiles.ItemIndex := itemIndex; + SetActiveProfile(TProfile(cmbProfiles.Items.Objects[itemIndex])); + finally + FLockChangeProfile := False; + end; + end; + end; +end; + + +procedure TMainForm.cmbProfilesClick(Sender: TObject); +begin + if not FLockChangeProfile then + begin + if cmbProfiles.ItemIndex > -1 then + SetActiveProfile(TProfile(cmbProfiles.Items.Objects[cmbProfiles.ItemIndex])); end; end; @@ -247,8 +626,39 @@ begin 'Do you want to automatically check for updates?', 'Check for updates', MB_YESNO or MB_ICONQUESTION) = ID_YES then begin cbCheckUpdates.Checked := True; + Settings.CheckUpdates := True; + CheckForUpdates(False); end; + + SaveSettings; +end; + + +procedure TMainForm.SetActiveProfile(const Value: TProfile); +begin + if Value <> FActiveProfile then + begin + FActiveProfile := Value; + + if Assigned(ActiveProfile) then + begin + if Settings.ActiveProfile <> ActiveProfile.Name then + begin + Settings.ActiveProfile := ActiveProfile.Name; + SaveSettings; + end; + + FLockChangeProfile := True; + try + cmbProfiles.ItemIndex := cmbProfiles.Items.IndexOfObject(ActiveProfile); + finally + FLockChangeProfile := False; + end; + + LoadActiveProfile; + end; + end; end; @@ -259,255 +669,83 @@ begin imgStateFound.Visible := AFound; imgStateNotFound.Visible := not AFound; + + FG940Found := AFound; end; -procedure TMainForm.SetFSXToggleZoomButton(const ADeviceGUID: TGUID; AButtonIndex: Integer; const ADisplayText: string); +procedure TMainForm.SetFSXState(const AMessage: string; AConnected: Boolean); begin - FFSXToggleZoomDeviceGUID := ADeviceGUID; - FFSXToggleZoomButtonIndex := AButtonIndex; - lblFSXToggleZoomButtonName.Caption := ADisplayText; + lblFSXState.Caption := AMessage; + lblFSXState.Update; + + imgFSXStateConnected.Visible := AConnected; + imgFSXStateNotConnected.Visible := not AConnected; end; -procedure TMainForm.LoadFunctions(AProviderClass: TLEDStateProviderClass; AComboBoxes: TComboBoxArray); -var - comboBox: TComboBoxEx; +procedure TMainForm.LEDButtonClick(Sender: TObject); + + function GetUniqueProfileName(const AName: string): string; + var + counter: Integer; -begin - for comboBox in AComboBoxes do begin - comboBox.Items.BeginUpdate; - try - comboBox.Items.Clear; - AProviderClass.EnumFunctions(TComboBoxFunctionConsumer.Create(comboBox)); + Result := AName; + counter := 0; - comboBox.ItemIndex := 0; - if Assigned(comboBox.OnChange) then - comboBox.OnChange(comboBox); - finally - comboBox.Items.EndUpdate; + while Assigned(Profiles.Find(Result)) do + begin + Inc(counter); + Result := Format('%s (%d)', [AName, counter]); end; end; -end; -procedure TMainForm.SetFunctions(AComboBoxes: TComboBoxArray); var - comboBox: TComboBoxEx; + buttonIndex: NativeInt; + profile: TProfile; + newProfile: Boolean; begin - for comboBox in AComboBoxes do + if not Assigned(ActiveProfile) then + exit; + + { Behaviour similar to the Windows System Sounds control panel; + when a change occurs, create a temporary profile "(modified)" + so the original profile can still be selected } + if not ActiveProfile.IsTemporary then begin - if comboBox.ItemIndex > -1 then - LEDStateConsumer.SetFunction(StateConsumerTask, comboBox.Tag, Integer(comboBox.ItemsEx[comboBox.ItemIndex].Data)); - end; -end; - - -procedure TMainForm.ReadFunctions(AReader: IX2PersistReader; AComboBoxes: TComboBoxArray); -var - comboBox: TComboBoxEx; - value: Integer; - itemIndex: Integer; - -begin - if AReader.BeginSection(SECTION_FSX) then - try - for comboBox in AComboBoxes do - begin - if AReader.ReadInteger('Function' + IntToStr(comboBox.Tag), value) then - begin - for itemIndex := 0 to Pred(comboBox.ItemsEx.Count) do - if Integer(comboBox.ItemsEx[itemIndex].Data) = value then - begin - comboBox.ItemIndex := itemIndex; - break; - end; - end; - end; - finally - AReader.EndSection; - end; -end; - - -procedure TMainForm.ReadFSXExtra(AReader: IX2PersistReader); -var - deviceGUID: string; - buttonIndex: Integer; - displayText: string; - -begin - if AReader.BeginSection(SECTION_FSX) then - try - if AReader.ReadString('ToggleZoomDeviceGUID', deviceGUID) and - AReader.ReadInteger('ToggleZoomButtonIndex', buttonIndex) and - AReader.ReadString('ToggleZoomDisplayText', displayText) then - begin - try - SetFSXToggleZoomButton(StringToGUID(deviceGUID), buttonIndex, displayText); - except - on E:EConvertError do; - end; - end; - finally - AReader.EndSection; - end; -end; - - -procedure TMainForm.ReadAutoUpdate(AReader: IX2PersistReader); -var - checkUpdates: Boolean; - askAutoUpdate: Boolean; - -begin - askAutoUpdate := True; - - if AReader.BeginSection(SECTION_SETTINGS) then - try - if AReader.ReadBoolean('CheckUpdates', checkUpdates) then - begin - cbCheckUpdates.Checked := checkUpdates; - askAutoUpdate := False; - end; - finally - AReader.EndSection; + profile := TProfile.Create; + profile.Assign(ActiveProfile); + profile.Name := GetUniqueProfileName(profile.Name + ProfilePostfixModified); + profile.IsTemporary := True; + newProfile := True; + end else + begin + profile := ActiveProfile; + newProfile := False; end; - if askAutoUpdate then - PostMessage(Self.Handle, CM_ASKAUTOUPDATE, 0, 0) - else if cbCheckUpdates.Checked then - CheckForUpdates(False); -end; + buttonIndex := (Sender as TComponent).Tag; + if TButtonFunctionForm.Execute(profile, buttonIndex) then + begin + if newProfile then + AddProfile(profile); + SaveProfiles; + UpdateButton(profile, buttonIndex); -procedure TMainForm.WriteFunctions(AWriter: IX2PersistWriter; AComboBoxes: TComboBoxArray); -var - comboBox: TComboBoxEx; - value: Integer; - -begin - if AWriter.BeginSection(SECTION_FSX) then - try - for comboBox in AComboBoxes do - begin - value := -1; - if comboBox.ItemIndex > -1 then - value := Integer(comboBox.ItemsEx[comboBox.ItemIndex].Data); - - AWriter.WriteInteger('Function' + IntToStr(comboBox.Tag), value); - end; - finally - AWriter.EndSection; + if Assigned(StateConsumerTask) then + StateConsumerTask.Comm.Send(TM_LOADPROFILE, profile); + end else + begin + if newProfile then + FreeAndNil(profile); end; end; -procedure TMainForm.WriteFSXExtra(AWriter: IX2PersistWriter); -begin - if AWriter.BeginSection(SECTION_FSX) then - try - AWriter.WriteString('ToggleZoomDeviceGUID', GUIDToString(FFSXToggleZoomDeviceGUID)); - AWriter.WriteInteger('ToggleZoomButtonIndex', FFSXToggleZoomButtonIndex); - AWriter.WriteString('ToggleZoomDisplayText', lblFSXToggleZoomButtonName.Caption); - // ToDo pressed / depressed levels - finally - AWriter.EndSection; - end; -end; - - -procedure TMainForm.WriteAutoUpdate(AWriter: IX2PersistWriter); -begin - if AWriter.BeginSection(SECTION_SETTINGS) then - try - AWriter.WriteBoolean('CheckUpdates', cbCheckUpdates.Checked); - finally - AWriter.EndSection; - end; -end; - - -procedure TMainForm.LoadDefaultProfile; -var - registryReader: TX2UtPersistRegistry; - reader: IX2PersistReader; - -begin - registryReader := TX2UtPersistRegistry.Create; - try - registryReader.RootKey := HKEY_CURRENT_USER; - registryReader.Key := KEY_SETTINGS; - - reader := registryReader.CreateReader; - - if reader.BeginSection(SECTION_DEFAULTPROFILE) then - try - ReadFunctions(reader, FFSXComboBoxes); - ReadFSXExtra(reader); - finally - reader.EndSection; - end; - - ReadAutoUpdate(reader); - finally - FreeAndNil(registryReader); - end; -end; - - -procedure TMainForm.SaveDefaultProfile; -var - registryWriter: TX2UtPersistRegistry; - writer: IX2PersistWriter; - -begin - registryWriter := TX2UtPersistRegistry.Create; - try - registryWriter.RootKey := HKEY_CURRENT_USER; - registryWriter.Key := KEY_SETTINGS; - - writer := registryWriter.CreateWriter; - if writer.BeginSection(SECTION_DEFAULTPROFILE) then - try - WriteFunctions(writer, FFSXComboBoxes); - WriteFSXExtra(writer); - finally - writer.EndSection; - end; - - WriteAutoUpdate(writer); - finally - FreeAndNil(registryWriter); - end; -end; - - -procedure TMainForm.InitializeStateProvider(AProviderClass: TLEDStateProviderClass); -begin - UpdateMapping; - LEDStateConsumer.InitializeStateProvider(StateConsumerTask, AProviderClass); -end; - - -procedure TMainForm.FinalizeStateProvider; -begin - LEDStateConsumer.FinalizeStateProvider(StateConsumerTask); -end; - - -procedure TMainForm.UpdateMapping; -begin - if not Assigned(StateConsumerTask) then - Exit; - - LEDStateConsumer.ClearFunctions(StateConsumerTask); - SetFunctions(FFSXComboBoxes); -end; - - function FetchNextNumber(var AValue: string; out ANumber: Integer): Boolean; var dotPos: Integer; @@ -575,11 +813,11 @@ begin try latestVersion := httpClient.Get('http://g940.x2software.net/version'); if VersionIsNewer(Format('%d.%d.%d', [App.Version.Major, App.Version.Minor, App.Version.Release]), latestVersion) then - ATask.Comm.Send(MSG_UPDATE) + ATask.Comm.Send(TM_UPDATE, latestVersion) else begin if ATask.Param.ByName('ReportNoUpdates').AsBoolean then - ATask.Comm.Send(MSG_NOUPDATE, True); + ATask.Comm.Send(TM_NOUPDATE, True); end; msgSent := True; @@ -591,12 +829,97 @@ begin on E:Exception do begin if not msgSent then - ATask.Comm.Send(MSG_NOUPDATE, False); + ATask.Comm.Send(TM_NOUPDATE, False); end; end; end; +procedure TMainForm.btnSaveProfileClick(Sender: TObject); +var + name: string; + profile: TProfile; + existingProfile: TProfile; + newProfile: TProfile; + +begin + name := ''; + profile := ActiveProfile; + existingProfile := nil; + + repeat + if InputQuery('Save profile as', 'Save this profile as:', name) then + begin + existingProfile := Profiles.Find(name); + if existingProfile = profile then + existingProfile := nil; + + if Assigned(existingProfile) then + begin + case MessageBox(Self.Handle, PChar(Format('A profile named "%s" exists, do you want to overwrite it?', [name])), + 'Save profile as', MB_ICONQUESTION or MB_YESNOCANCEL) of + ID_YES: + break; + + ID_CANCEL: + exit; + end; + end else + break; + end else + exit; + until False; + + if Assigned(existingProfile) then + begin + existingProfile.Assign(profile); + existingProfile.Name := name; + UpdateProfile(existingProfile); + SetActiveProfile(existingProfile); + + if profile.IsTemporary then + DeleteProfile(profile, False); + end else + begin + if profile.IsTemporary then + begin + profile.Name := name; + profile.IsTemporary := False; + UpdateProfile(profile); + end else + begin + newProfile := TProfile.Create; + newProfile.Assign(profile); + newProfile.Name := name; + AddProfile(newProfile); + end; + end; + + SaveProfiles; +end; + + +procedure TMainForm.btnDeleteProfileClick(Sender: TObject); +begin + if Assigned(ActiveProfile) then + begin + if MessageBox(Self.Handle, PChar(Format('Do you want to remove the profile named "%s"?', [ActiveProfile.Name])), + 'Remove profile', MB_ICONQUESTION or MB_YESNO) = ID_YES then + begin + DeleteProfile(ActiveProfile, True); + SaveProfiles; + end; + end; +end; + + +procedure TMainForm.cbCheckUpdatesClick(Sender: TObject); +begin + Settings.CheckUpdates := cbCheckUpdates.Checked; + SaveSettings; +end; + + procedure TMainForm.CheckForUpdates(AReportNoUpdates: Boolean); begin btnCheckUpdates.Enabled := False; @@ -611,16 +934,19 @@ end; procedure TMainForm.EventMonitorMessage(const task: IOmniTaskControl; const msg: TOmniMessage); begin case msg.MsgID of - MSG_NOTIFY_DEVICESTATE: HandleDeviceStateMessage(task, msg); - MSG_RUN_IN_MAINTHREAD: HandleRunInMainThreadMessage(task, msg); - MSG_PROVIDER_KILLED: HandleProviderKilled(task, msg); + TM_NOTIFY_DEVICESTATE: + HandleDeviceStateMessage(msg); - MSG_UPDATE: - if MessageBox(Self.Handle, 'An update is available on the G940 LED Control website.'#13#10'Do you want to go there now?', - 'Update available', MB_YESNO or MB_ICONINFORMATION) = ID_YES then - ShellExecute(Self.Handle, 'open', PChar('http://g940.x2software.net/#download'), nil, nil, SW_SHOWNORMAL); + TM_FSXSTATE: + HandleFSXStateMessage(msg); - MSG_NOUPDATE: + TM_UPDATE: + if MessageBox(Self.Handle, PChar('Version ' + msg.MsgData + ' is available on the G940 LED Control website.'#13#10 + + 'Do you want to open the website now?'), 'Update available', + MB_YESNO or MB_ICONINFORMATION) = ID_YES then + ShellExecute(Self.Handle, 'open', PChar('http://g940.x2software.net/category/releases/'), nil, nil, SW_SHOWNORMAL); + + TM_NOUPDATE: if msg.MsgData.AsBoolean then MessageBox(Self.Handle, 'You are using the latest version.', 'No update available', MB_OK or MB_ICONINFORMATION) else @@ -640,52 +966,38 @@ begin end; -procedure TMainForm.HandleDeviceStateMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); +procedure TMainForm.HandleDeviceStateMessage(AMessage: TOmniMessage); begin case AMessage.MsgData.AsInteger of DEVICESTATE_SEARCHING: - SetDeviceState(TEXT_STATE_SEARCHING, False); + SetDeviceState(TextStateSearching, False); DEVICESTATE_FOUND: - SetDeviceState(TEXT_STATE_FOUND, True); + SetDeviceState(TextStateFound, True); DEVICESTATE_NOTFOUND: - begin - SetDeviceState(TEXT_STATE_NOTFOUND, False); - btnRetry.Visible := True; - end; + SetDeviceState(TextStateNotFound, False); end; end; -procedure TMainForm.HandleRunInMainThreadMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); +procedure TMainForm.HandleFSXStateMessage(AMessage: TOmniMessage); var - executor: IRunInMainThread; + state: TFSXSimConnectState; begin - executor := (AMessage.MsgData.AsInterface as IRunInMainThread); - executor.Execute; - executor.Signal; -end; + state := TFSXSimConnectState(AMessage.MsgData.AsInteger); + case state of + scsDisconnected: + SetFSXState(TextFSXDisconnected, False); -procedure TMainForm.HandleProviderKilled(ATask: IOmniTaskControl; AMessage: TOmniMessage); -begin - HandleProviderKilledFSX(ATask, AMessage); -end; + scsConnected: + SetFSXState(TextFSXConnected, True); - -procedure TMainForm.HandleProviderKilledFSX(ATask: IOmniTaskControl; AMessage: TOmniMessage); -var - msg: string; - -begin - btnFSXDisconnect.Enabled := False; - btnFSXConnect.Enabled := True; - - msg := AMessage.MsgData; - if Length(msg) > 0 then - ShowMessage(msg); + scsFailed: + SetFSXState(TextFSXFailed, False); + end; end; @@ -694,59 +1006,6 @@ begin CheckForUpdates(True); end; -procedure TMainForm.btnFSXConnectClick(Sender: TObject); -begin - SaveDefaultProfile; - InitializeStateProvider(TFSXLEDStateProvider); - - btnFSXDisconnect.Enabled := True; - btnFSXConnect.Enabled := False; -end; - - -procedure TMainForm.btnFSXDisconnectClick(Sender: TObject); -begin - FinalizeStateProvider; - btnFSXDisconnect.Enabled := False; - btnFSXConnect.Enabled := True; -end; - - -procedure TMainForm.btnFSXToggleZoomClick(Sender: TObject); -var - deviceGUID: TGUID; - button: Integer; - displayText: string; - -begin - FillChar(deviceGUID, SizeOf(deviceGUID), 0); - button := -1; - - if TButtonSelectForm.Execute(deviceGUID, button, displayText) then - SetFSXToggleZoomButton(deviceGUID, button, displayText); -end; - - -procedure TMainForm.btnRetryClick(Sender: TObject); -begin - btnRetry.Visible := False; - StateConsumerTask.Comm.Send(MSG_FINDTHROTTLEDEVICE); -end; - - -procedure TMainForm.FunctionComboBoxChange(Sender: TObject); -var - comboBox: TComboBoxEx; - -begin - comboBox := TComboBoxEx(Sender); - if comboBox.ItemIndex > -1 then - begin - if not Assigned(comboBox.ItemsEx[comboBox.ItemIndex].Data) then - comboBox.ItemIndex := Succ(comboBox.ItemIndex); - end; -end; - procedure TMainForm.lblLinkLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType); begin @@ -754,33 +1013,26 @@ begin end; -{ TComboBoxFunctionConsumer } -constructor TComboBoxFunctionConsumer.Create(AComboBox: TComboBoxEx); +{ TFSXStateMonitorWorker } +function TFSXStateMonitorWorker.Initialize: Boolean; begin - inherited Create; + Result := inherited Initialize; - FComboBox := AComboBox; + if Result then + TFSXSimConnectStateMonitor.Instance.Attach(Self); end; -procedure TComboBoxFunctionConsumer.SetCategory(const ACategory: string); +procedure TFSXStateMonitorWorker.Cleanup; begin - with ComboBox.ItemsEx.Add do - begin - Caption := ACategory; - Data := nil; - end; + TFSXSimConnectStateMonitor.Instance.Detach(Self); + + inherited Cleanup; end; - -procedure TComboBoxFunctionConsumer.AddFunction(AFunction: Integer; const ADescription: string); +procedure TFSXStateMonitorWorker.ObserverStateUpdate(ANewState: TFSXSimConnectState); begin - with ComboBox.ItemsEx.Add do - begin - Caption := ADescription; - Indent := 1; - Data := Pointer(AFunction); - end; + Task.Comm.Send(TM_FSXSTATE, Integer(ANewState)); end; end. diff --git a/G940LEDControl/G940LEDControl.dpr b/G940LEDControl/G940LEDControl.dpr index 0510214..87b0bc9 100644 --- a/G940LEDControl/G940LEDControl.dpr +++ b/G940LEDControl/G940LEDControl.dpr @@ -5,19 +5,39 @@ uses MainFrm in 'Forms\MainFrm.pas' {MainForm}, LogiJoystickDLL in '..\Shared\LogiJoystickDLL.pas', SimConnect in '..\Shared\SimConnect.pas', - ButtonSelectFrm in 'Forms\ButtonSelectFrm.pas' {ButtonSelectForm}, - FSXLEDStateProvider in 'Units\FSXLEDStateProvider.pas', G940LEDStateConsumer in 'Units\G940LEDStateConsumer.pas', - LEDFunctionMap in 'Units\LEDFunctionMap.pas', LEDStateConsumer in 'Units\LEDStateConsumer.pas', - LEDStateProvider in 'Units\LEDStateProvider.pas'; + LEDColorIntf in 'Units\LEDColorIntf.pas', + LEDColor in 'Units\LEDColor.pas', + LEDFunctionIntf in 'Units\LEDFunctionIntf.pas', + LEDFunction in 'Units\LEDFunction.pas', + StaticLEDFunction in 'Units\StaticLEDFunction.pas', + ConfigConversion in 'Units\ConfigConversion.pas', + LEDFunctionRegistry in 'Units\LEDFunctionRegistry.pas', + StaticLEDColor in 'Units\StaticLEDColor.pas', + DynamicLEDColor in 'Units\DynamicLEDColor.pas', + LEDStateIntf in 'Units\LEDStateIntf.pas', + LEDState in 'Units\LEDState.pas', + Profile in 'Units\Profile.pas', + LEDColorPool in 'Units\LEDColorPool.pas', + ButtonFunctionFrm in 'Forms\ButtonFunctionFrm.pas' {ButtonFunctionForm}, + FSXLEDFunctionProvider in 'Units\FSXLEDFunctionProvider.pas', + StaticResources in 'Units\StaticResources.pas', + FSXResources in 'Units\FSXResources.pas', + FSXSimConnectClient in 'Units\FSXSimConnectClient.pas', + FSXSimConnectIntf in 'Units\FSXSimConnectIntf.pas', + FSXLEDFunction in 'Units\FSXLEDFunction.pas', + LEDResources in 'Units\LEDResources.pas', + Settings in 'Units\Settings.pas', + FSXLEDFunctionWorker in 'Units\FSXLEDFunctionWorker.pas', + FSXSimConnectStateMonitor in 'Units\FSXSimConnectStateMonitor.pas'; {$R *.res} var MainForm: TMainForm; - + begin Application.Initialize; Application.MainFormOnTaskbar := True; diff --git a/G940LEDControl/G940LEDControl.dproj b/G940LEDControl/G940LEDControl.dproj index 4a20209..df155fb 100644 --- a/G940LEDControl/G940LEDControl.dproj +++ b/G940LEDControl/G940LEDControl.dproj @@ -8,7 +8,7 @@ VCL 13.4 True - Release + Debug Win32 1 Application @@ -49,6 +49,7 @@ true + rtl;dbrtl;$(DCC_UsePackage) Lib 0 Bin @@ -81,9 +82,9 @@ RELEASE;$(DCC_Define) - 1 - 6 - CompanyName=X²Software;FileDescription=G940 LED Control;FileVersion=0.6.1.0;InternalName=;LegalCopyright=© 2011 X²Software;LegalTrademarks=;OriginalFilename=G940LEDControl.exe;ProductName=G940 LED Control;ProductVersion=0.6.1;Comments= + 1 + 0 + CompanyName=X²Software;FileDescription=G940 LED Control;FileVersion=1.0.0.0;InternalName=;LegalCopyright=© 2011 X²Software;LegalTrademarks=;OriginalFilename=G940LEDControl.exe;ProductName=G940 LED Control;ProductVersion=1.0;Comments= 1033 $(BDS)\bin\default_app.manifest @@ -145,6 +146,110 @@ G940LEDControl.dpr + ExpressCoreLibrary by Developer Express Inc. + Express Cross Platform Library by Developer Express Inc. + ExpressPageControl by Developer Express Inc. + ExpressEditors Library by Developer Express Inc. + ExpressBars by Developer Express Inc. + ExpressBars Ribbon controls by Developer Express Inc. + ExpressScheduler by Developer Express Inc. + ExpressSkins Library by Developer Express Inc. + ExpressPrinting System by Developer Express Inc. + ExpressPivotGrid by Developer Express Inc. + ExpressOrgChart by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper by Developer Express Inc. + ExpressSkins Library Painter for PageControl by Developer Express Inc. + ExpressSkins Library Painter for Scheduler by Developer Express Inc. + ExpressSkins Library Painter for Bars by Developer Express Inc. + ExpressSkins Library Painter for NavBar by Developer Express Inc. + ExpressSkins Library Painter for Ribbon by Developer Express Inc. + ExpressSkins Library Painter for Docking Library by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressLayoutControl by Developer Express Inc. + ExpressEditors FieldLink by Developer Express Inc. + ExpressBars DBNavigator by Developer Express Inc. + ExpressBars extended DB items by Developer Express Inc. + ExpressBars extended items by Developer Express Inc. + ExpressBars Tabbed MDI by Developer Express Inc. + ExpressLayout Control by Developer Express Inc. + ExpressQuantumTreeList 5 by Developer Express Inc. + ExpressQuantumGrid by Developer Express Inc. + ExpressVerticalGrid by Developer Express Inc. + ExpressMemData by Developer Express Inc. + ExpressSpellChecker 2 by Developer Express Inc. + ExpressSpreadSheet by Developer Express Inc. + ExpressDocking Library by Developer Express Inc. + ExpressNavBar by Developer Express Inc. + ExpressSkins - Black Skin by Developer Express Inc. + ExpressSkins - Blue Skin by Developer Express Inc. + ExpressSkins - Blueprint Skin by Developer Express Inc. + ExpressSkins - Caramel Skin by Developer Express Inc. + ExpressSkins - Coffee Skin by Developer Express Inc. + ExpressSkins - Darkroom Skin by Developer Express Inc. + ExpressSkins - DarkSide Skin by Developer Express Inc. + ExpressSkins - DevExpressDarkStyle Skin by Developer Express Inc. + ExpressSkins - DevExpressStyle Skin by Developer Express Inc. + ExpressSkins - Foggy Skin by Developer Express Inc. + ExpressSkins - GlassOceans Skin by Developer Express Inc. + ExpressSkins - HighContrast Skin by Developer Express Inc. + ExpressSkins - iMaginary Skin by Developer Express Inc. + ExpressSkins - Lilian Skin by Developer Express Inc. + ExpressSkins - LiquidSky Skin by Developer Express Inc. + ExpressSkins - LondonLiquidSky Skin by Developer Express Inc. + ExpressSkins - McSkin Skin by Developer Express Inc. + ExpressSkins - MoneyTwins Skin by Developer Express Inc. + ExpressSkins - Office2007Black Skin by Developer Express Inc. + ExpressSkins - Office2007Blue Skin by Developer Express Inc. + ExpressSkins - Office2007Green Skin by Developer Express Inc. + ExpressSkins - Office2007Pink Skin by Developer Express Inc. + ExpressSkins - Office2007Silver Skin by Developer Express Inc. + ExpressSkins - Office2010Black Skin by Developer Express Inc. + ExpressSkins - Office2010Blue Skin by Developer Express Inc. + ExpressSkins - Office2010Silver Skin by Developer Express Inc. + ExpressSkins - Pumpkin Skin by Developer Express Inc. + ExpressSkins - SevenClassic Skin by Developer Express Inc. + ExpressSkins - Seven Skin by Developer Express Inc. + ExpressSkins - Sharp Skin by Developer Express Inc. + ExpressSkins - SharpPlus Skin by Developer Express Inc. + ExpressSkins - Silver Skin by Developer Express Inc. + ExpressSkins - Springtime Skin by Developer Express Inc. + ExpressSkins - Stardust Skin by Developer Express Inc. + ExpressSkins - Summer2008 Skin by Developer Express Inc. + ExpressSkins - TheAsphaltWorld Skin by Developer Express Inc. + ExpressSkins - Valentine Skin by Developer Express Inc. + ExpressSkins - VS2010 Skin by Developer Express Inc. + ExpressSkins - Whiteprint Skin by Developer Express Inc. + ExpressSkins - Xmas2008Blue Skin by Developer Express Inc. + ExpressPrinting System ReportLinks (Standard) by Developer Express Inc. + ExpressPrinting System ContainerProducer for ExpressPageControl by Developer Express Inc. + ExpressDBTree by Developer Express Inc. + ExpressTreePrintedDataSet by Developer Express Inc. + ExpressDBOrgChart by Developer Express Inc. + ExpressFlowChart by Developer Express Inc. + ExpressPageControl dxBar Popup Menu by Developer Express Inc. + ExpressBars cxEditor item by Developer Express Inc. + ExpressScheduler connection to ExpressQuantumGrid by Developer Express Inc. + ExpressQuantumTreeList 5 dxBar Built-In Menu by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for ExpressEditors by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for PageControl Painter by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for Scheduler Painter by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for Bars Painters by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for NavBar Painter by Developer Express Inc. + ExpressSkins Library Uses Clause Auto Fill Helper for Ribbon Painters by Developer Express Inc. + ExpressPrinting System Cross Platform Library by Developer Express Inc. + ExpressPrinting System Extended Cross Platform Library by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressPivotGrid by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressScheduler by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressSpreadSheet by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressQuantumTreeList by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressVerticalGrid by Developer Express Inc. + ExpressPrinting System ReportLinks for ExpressDBOrgChart by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressDBTree by Developer Express Inc. + ExpressPrinting System ReportLinks for ExpressFlowChart by Developer Express Inc. + ExpressPrinting System ReportLink for ExpressQuantumGrid by Developer Express Inc. + ExpressPrinting System ReportLinks for ExpressOrgChart by Developer Express Inc. + ExpressPrinting System Advanced Preview Window by Developer Express Inc. + ExpressPrinting System Ribbon Preview Window by Developer Express Inc. + ExpressPivotGrid 2 connection to ExpressQuantumGrid Chart View by Developer Express Inc. ExpressPivotGrid 2 OLAP by Developer Express Inc. @@ -165,15 +270,35 @@ - -
ButtonSelectForm
+ + + + + + + + + + + + + + + + +
ButtonFunctionForm
dfm
- - - - - + + + + + + + + + + Cfg_2 Base diff --git a/G940LEDControl/G940LEDControl.res b/G940LEDControl/G940LEDControl.res index b287848..b2c5061 100644 Binary files a/G940LEDControl/G940LEDControl.res and b/G940LEDControl/G940LEDControl.res differ diff --git a/G940LEDControl/Resources/Images/FSXConnected.png b/G940LEDControl/Resources/Images/FSXConnected.png new file mode 100644 index 0000000..d67a18e Binary files /dev/null and b/G940LEDControl/Resources/Images/FSXConnected.png differ diff --git a/G940LEDControl/Resources/Images/FSXDisconnected.png b/G940LEDControl/Resources/Images/FSXDisconnected.png new file mode 100644 index 0000000..d4a892d Binary files /dev/null and b/G940LEDControl/Resources/Images/FSXDisconnected.png differ diff --git a/G940LEDControl/Resources/Images/Found.png b/G940LEDControl/Resources/Images/Found.png index 0d56a57..1b53fa9 100644 Binary files a/G940LEDControl/Resources/Images/Found.png and b/G940LEDControl/Resources/Images/Found.png differ diff --git a/G940LEDControl/Resources/Images/NotFound.png b/G940LEDControl/Resources/Images/NotFound.png index 5b84d6c..738d1c2 100644 Binary files a/G940LEDControl/Resources/Images/NotFound.png and b/G940LEDControl/Resources/Images/NotFound.png differ diff --git a/G940LEDControl/Units/ConfigConversion.pas b/G940LEDControl/Units/ConfigConversion.pas new file mode 100644 index 0000000..b86bbc8 --- /dev/null +++ b/G940LEDControl/Units/ConfigConversion.pas @@ -0,0 +1,254 @@ +unit ConfigConversion; + +interface +uses + Profile, + Settings; + + { Version 0.x: registry -> 1.x: XML } + function ConvertProfile0To1: TProfile; + function ConvertSettings0To1: TSettings; + + +implementation +uses + System.SysUtils, + Winapi.Windows, + + X2UtPersistIntf, + X2UtPersistRegistry, + + FSXResources, + LEDColorIntf, + StaticResources; + + +const + V0_FUNCTION_NONE = 0; + V0_FUNCTION_OFF = 1; + V0_FUNCTION_RED = 2; + V0_FUNCTION_AMBER = 3; + V0_FUNCTION_GREEN = 4; + V0_FUNCTIONPROVIDER_OFFSET = V0_FUNCTION_GREEN; + + + V0_FUNCTIONFSX_GEAR = V0_FUNCTIONPROVIDER_OFFSET + 1; + V0_FUNCTIONFSX_LANDINGLIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 2; + V0_FUNCTIONFSX_INSTRUMENTLIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 3; + V0_FUNCTIONFSX_PARKINGBRAKE = V0_FUNCTIONPROVIDER_OFFSET + 4; + V0_FUNCTIONFSX_ENGINE = V0_FUNCTIONPROVIDER_OFFSET + 5; + + V0_FUNCTIONFSX_EXITDOOR = V0_FUNCTIONPROVIDER_OFFSET + 6; + V0_FUNCTIONFSX_STROBELIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 7; + V0_FUNCTIONFSX_NAVLIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 8; + V0_FUNCTIONFSX_BEACONLIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 9; + V0_FUNCTIONFSX_FLAPS = V0_FUNCTIONPROVIDER_OFFSET + 10; + V0_FUNCTIONFSX_BATTERYMASTER = V0_FUNCTIONPROVIDER_OFFSET + 11; + V0_FUNCTIONFSX_AVIONICSMASTER = V0_FUNCTIONPROVIDER_OFFSET + 12; + + V0_FUNCTIONFSX_SPOILERS = V0_FUNCTIONPROVIDER_OFFSET + 13; + + V0_FUNCTIONFSX_PRESSURIZATIONDUMPSWITCH = V0_FUNCTIONPROVIDER_OFFSET + 14; + V0_FUNCTIONFSX_ENGINEANTIICE = V0_FUNCTIONPROVIDER_OFFSET + 15; + V0_FUNCTIONFSX_AUTOPILOT = V0_FUNCTIONPROVIDER_OFFSET + 16; + V0_FUNCTIONFSX_FUELPUMP = V0_FUNCTIONPROVIDER_OFFSET + 17; + + V0_FUNCTIONFSX_TAILHOOK = V0_FUNCTIONPROVIDER_OFFSET + 18; + + V0_FUNCTIONFSX_AUTOPILOT_AMBER = V0_FUNCTIONPROVIDER_OFFSET + 19; + V0_FUNCTIONFSX_AUTOPILOT_HEADING = V0_FUNCTIONPROVIDER_OFFSET + 20; + V0_FUNCTIONFSX_AUTOPILOT_APPROACH = V0_FUNCTIONPROVIDER_OFFSET + 21; + V0_FUNCTIONFSX_AUTOPILOT_BACKCOURSE = V0_FUNCTIONPROVIDER_OFFSET + 22; + V0_FUNCTIONFSX_AUTOPILOT_ALTITUDE = V0_FUNCTIONPROVIDER_OFFSET + 23; + V0_FUNCTIONFSX_AUTOPILOT_NAV = V0_FUNCTIONPROVIDER_OFFSET + 24; + + V0_FUNCTIONFSX_TAXILIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 25; + V0_FUNCTIONFSX_RECOGNITIONLIGHTS = V0_FUNCTIONPROVIDER_OFFSET + 26; + + V0_FUNCTIONFSX_DEICE = V0_FUNCTIONPROVIDER_OFFSET + 27; + + + +procedure ConvertProfileFunction0To1(AOldFunction: Integer; AButton: TProfileButton); + + procedure SetButton(const AProviderUID, AFunctionUID: string); + begin + AButton.ProviderUID := AProviderUID; + AButton.FunctionUID := AFunctionUID; + end; + + +begin + { Default states are handled by the specific functions } + case AOldFunction of + { Static } + V0_FUNCTION_OFF: SetButton(StaticProviderUID, StaticFunctionUID[lcOff]); + V0_FUNCTION_RED: SetButton(StaticProviderUID, StaticFunctionUID[lcRed]); + V0_FUNCTION_AMBER: SetButton(StaticProviderUID, StaticFunctionUID[lcAmber]); + V0_FUNCTION_GREEN: SetButton(StaticProviderUID, StaticFunctionUID[lcGreen]); + + { FSX } + V0_FUNCTIONFSX_GEAR: SetButton(FSXProviderUID, FSXFunctionUIDGear); + V0_FUNCTIONFSX_LANDINGLIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDLandingLights); + V0_FUNCTIONFSX_INSTRUMENTLIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDInstrumentLights); + V0_FUNCTIONFSX_PARKINGBRAKE: SetButton(FSXProviderUID, FSXFunctionUIDParkingBrake); + V0_FUNCTIONFSX_ENGINE: SetButton(FSXProviderUID, FSXFunctionUIDEngine); + + V0_FUNCTIONFSX_EXITDOOR: SetButton(FSXProviderUID, FSXFunctionUIDExitDoor); + V0_FUNCTIONFSX_STROBELIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDStrobeLights); + V0_FUNCTIONFSX_NAVLIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDNavLights); + V0_FUNCTIONFSX_BEACONLIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDBeaconLights); + V0_FUNCTIONFSX_FLAPS: SetButton(FSXProviderUID, FSXFunctionUIDFlaps); + V0_FUNCTIONFSX_BATTERYMASTER: SetButton(FSXProviderUID, FSXFunctionUIDBatteryMaster); + V0_FUNCTIONFSX_AVIONICSMASTER: SetButton(FSXProviderUID, FSXFunctionUIDAvionicsMaster); + V0_FUNCTIONFSX_SPOILERS: SetButton(FSXProviderUID, FSXFunctionUIDSpoilers); + V0_FUNCTIONFSX_PRESSURIZATIONDUMPSWITCH: SetButton(FSXProviderUID, FSXFunctionUIDPressDumpSwitch); + V0_FUNCTIONFSX_ENGINEANTIICE: SetButton(FSXProviderUID, FSXFunctionUIDEngineAntiIce); + V0_FUNCTIONFSX_AUTOPILOT: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilot); + AButton.SetStateColor(FSXStateUIDOn, lcGreen); + AButton.SetStateColor(FSXStateUIDOff, lcRed); + end; + + V0_FUNCTIONFSX_TAILHOOK: SetButton(FSXProviderUID, FSXFunctionUIDTailHook); + V0_FUNCTIONFSX_AUTOPILOT_AMBER: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilot); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_AUTOPILOT_HEADING: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilotHeading); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_AUTOPILOT_APPROACH: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilotApproach); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_AUTOPILOT_BACKCOURSE: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilotBackcourse); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_AUTOPILOT_ALTITUDE: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilotAltitude); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_AUTOPILOT_NAV: + begin + { The new default is Green / Off } + SetButton(FSXProviderUID, FSXFunctionUIDAutoPilotNav); + AButton.SetStateColor(FSXStateUIDOn, lcAmber); + AButton.SetStateColor(FSXStateUIDOff, lcOff); + end; + + V0_FUNCTIONFSX_TAXILIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDTaxiLights); + V0_FUNCTIONFSX_RECOGNITIONLIGHTS: SetButton(FSXProviderUID, FSXFunctionUIDRecognitionLights); + V0_FUNCTIONFSX_DEICE: SetButton(FSXProviderUID, FSXFunctionUIDDeIce); + end; +end; + + +function ConvertProfile0To1: TProfile; +const + KEY_SETTINGS = '\Software\X2Software\G940LEDControl\'; + SECTION_DEFAULTPROFILE = 'DefaultProfile'; + SECTION_FSX = 'FSX'; + +var + registryReader: TX2UtPersistRegistry; + reader: IX2PersistReader; + buttonIndex: Integer; + value: Integer; + +begin + Result := nil; + + registryReader := TX2UtPersistRegistry.Create; + try + registryReader.RootKey := HKEY_CURRENT_USER; + registryReader.Key := KEY_SETTINGS; + + reader := registryReader.CreateReader; + + if reader.BeginSection(SECTION_DEFAULTPROFILE) then + try + if reader.BeginSection(SECTION_FSX) then + try + for buttonIndex := 0 to 7 do + begin + if reader.ReadInteger('Function' + IntToStr(buttonIndex), value) then + begin + if not Assigned(Result) then + Result := TProfile.Create; + + ConvertProfileFunction0To1(value, Result.Buttons[buttonIndex]); + end; + end; + finally + reader.EndSection; + end; + finally + reader.EndSection; + end; + finally + FreeAndNil(registryReader); + end; +end; + + +function ConvertSettings0To1: TSettings; +const + KEY_SETTINGS = '\Software\X2Software\G940LEDControl\'; + SECTION_SETTINGS = 'Settings'; + +var + registryReader: TX2UtPersistRegistry; + reader: IX2PersistReader; + value: Boolean; + +begin + Result := nil; + + registryReader := TX2UtPersistRegistry.Create; + try + registryReader.RootKey := HKEY_CURRENT_USER; + registryReader.Key := KEY_SETTINGS; + + reader := registryReader.CreateReader; + + if reader.BeginSection(SECTION_SETTINGS) then + try + if reader.ReadBoolean('CheckUpdates', value) then + begin + Result := TSettings.Create; + Result.CheckUpdates := value; + end; + finally + reader.EndSection; + end; + finally + FreeAndNil(registryReader); + end; +end; + +end. diff --git a/G940LEDControl/Units/DynamicLEDColor.pas b/G940LEDControl/Units/DynamicLEDColor.pas new file mode 100644 index 0000000..178e1c2 --- /dev/null +++ b/G940LEDControl/Units/DynamicLEDColor.pas @@ -0,0 +1,83 @@ +unit DynamicLEDColor; + +interface +uses + LEDColor, + LEDColorIntf; + + +const + TICKINTERVAL_NORMAL = 2; + TICKINTERVAL_FAST = 1; + + +type + TStaticLEDColorDynArray = array of TStaticLEDColor; + + + TDynamicLEDColor = class(TCustomLEDStateDynamicColor) + private + FCycleColors: TStaticLEDColorDynArray; + FCycleIndex: Integer; + FTickInterval: Integer; + FTickCount: Integer; + protected + { ILEDState } + function GetCurrentColor: TStaticLEDColor; override; + + { ITickLEDState } + procedure Reset; override; + procedure Tick; override; + public + constructor Create(ACycleColors: TStaticLEDColorDynArray; ATickInterval: Integer = TICKINTERVAL_NORMAL); + end; + + + +implementation +uses + SysUtils; + + +{ TDynamicLEDState } +constructor TDynamicLEDColor.Create(ACycleColors: TStaticLEDColorDynArray; ATickInterval: Integer); +begin + inherited Create; + + if Length(ACycleColors) = 0 then + raise Exception.Create(Self.ClassName + ' must have at least one color in a cycle'); + + FCycleColors := ACycleColors; + FCycleIndex := Low(FCycleColors); + FTickInterval := ATickInterval; + Reset; +end; + + +function TDynamicLEDColor.GetCurrentColor: TStaticLEDColor; +begin + Result := FCycleColors[FCycleIndex]; +end; + + +procedure TDynamicLEDColor.Reset; +begin + FCycleIndex := 0; +end; + + +procedure TDynamicLEDColor.Tick; +begin + Inc(FTickCount); + + if FTickCount >= FTickInterval then + begin + Inc(FCycleIndex); + if FCycleIndex > High(FCycleColors) then + FCycleIndex := 0; + + FTickCount := 0; + end; +end; + +end. diff --git a/G940LEDControl/Units/FSXLEDFunction.pas b/G940LEDControl/Units/FSXLEDFunction.pas new file mode 100644 index 0000000..0206860 --- /dev/null +++ b/G940LEDControl/Units/FSXLEDFunction.pas @@ -0,0 +1,555 @@ +unit FSXLEDFunction; + +interface +uses + FSXLEDFunctionProvider, + LEDFunction, + LEDFunctionIntf; + + +type + TCustomFSXOnOffFunction = class(TCustomFSXFunction) + protected + procedure RegisterStates; override; + end; + + TCustomFSXInvertedOnOffFunction = class(TCustomFSXFunction) + protected + procedure RegisterStates; override; + end; + + { Systems } + TCustomFSXSystemsFunction = class(TCustomFSXFunction) + protected + function GetCategoryName: string; override; + end; + + TFSXBatteryMasterFunction = class(TCustomFSXOnOffFunction) + protected + function GetCategoryName: string; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXDeIceFunction = class(TCustomFSXInvertedOnOffFunction) + protected + function GetCategoryName: string; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXExitDoorFunction = class(TCustomFSXSystemsFunction) + protected + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXGearFunction = class(TCustomFSXSystemsFunction) + protected + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXParkingBrakeFunction = class(TCustomFSXInvertedOnOffFunction) + protected + function GetCategoryName: string; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXPressDumpSwitchFunction = class(TCustomFSXInvertedOnOffFunction) + protected + function GetCategoryName: string; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXTailHookFunction = class(TCustomFSXSystemsFunction) + protected + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + + { Engines } + TFSXEngineAntiIceFunction = class(TCustomFSXFunction) + protected + function GetCategoryName: string; override; + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXEngineFunction = class(TCustomFSXFunction) + protected + function GetCategoryName: string; override; + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + + { Control surfaces } + TFSXFlapsFunction = class(TCustomFSXFunction) + protected + function GetCategoryName: string; override; + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXSpoilersFunction = class(TCustomFSXFunction) + protected + function GetCategoryName: string; override; + procedure RegisterStates; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + + { Lights } + TCustomFSXLightFunction = class(TCustomFSXOnOffFunction) + protected + function GetCategoryName: string; override; + + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + function DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): TCustomLEDFunctionWorker; override; + protected + function GetLightMask: Integer; virtual; abstract; + end; + + TFSXLandingLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXInstrumentLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXBeaconLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXNavLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXStrobeLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXTaxiLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + TFSXRecognitionLightsFunction = class(TCustomFSXLightFunction) + protected + function GetLightMask: Integer; override; + end; + + + { Autopilot } + TCustomFSXAutoPilotFunction = class(TCustomFSXFunction) + protected + procedure RegisterStates; override; + function GetCategoryName: string; override; + end; + + TFSXAutoPilotFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXAutoPilotHeadingFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXAutoPilotApproachFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXAutoPilotBackcourseFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXAutoPilotAltitudeFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + TFSXAutoPilotNavFunction = class(TCustomFSXAutoPilotFunction) + protected + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + + { Radios } + TFSXAvionicsMasterFunction = class(TCustomFSXOnOffFunction) + protected + function GetCategoryName: string; override; + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; + end; + + +implementation +uses + FSXLEDFunctionWorker, + FSXResources, + FSXSimConnectIntf, + LEDColorIntf, + LEDState; + + +{ TFSXOnOffFunction } +procedure TCustomFSXOnOffFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDOn, FSXStateDisplayNameOn, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDOff, FSXStateDisplayNameOff, lcRed)); +end; + + +{ TCustomFSXInvertedOnOffFunction } +procedure TCustomFSXInvertedOnOffFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDOn, FSXStateDisplayNameOn, lcRed)); + RegisterState(TLEDState.Create(FSXStateUIDOff, FSXStateDisplayNameOff, lcGreen)); +end; + + +{ TCustomFSXSystemsFunction } +function TCustomFSXSystemsFunction.GetCategoryName: string; +begin + Result := FSXCategorySystems; +end; + + +{ TFSXBatteryMasterFunction } +function TFSXBatteryMasterFunction.GetCategoryName: string; +begin + Result := FSXCategorySystems; +end; + + +function TFSXBatteryMasterFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXBatteryMasterFunctionWorker; +end; + + +{ TFSXDeIceFunction } +function TFSXDeIceFunction.GetCategoryName: string; +begin + Result := FSXCategorySystems; +end; + + +function TFSXDeIceFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXDeIceFunctionWorker; +end; + + +{ TFSXExitDoorFunction } +procedure TFSXExitDoorFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDExitDoorClosed, FSXStateDisplayNameExitDoorClosed, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDExitDoorBetween, FSXStateDisplayNameExitDoorBetween, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDExitDoorOpen, FSXStateDisplayNameExitDoorOpen, lcRed)); +end; + + +function TFSXExitDoorFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXExitDoorFunctionWorker; +end; + + +{ TFSXGearFunction } +procedure TFSXGearFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDGearNotRetractable, FSXStateDisplayNameGearNotRetractable, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDGearRetracted, FSXStateDisplayNameGearRetracted, lcRed)); + RegisterState(TLEDState.Create(FSXStateUIDGearBetween, FSXStateDisplayNameGearBetween, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDGearExtended, FSXStateDisplayNameGearExtended, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDGearSpeedExceeded, FSXStateDisplayNameGearSpeedExceeded, lcFlashingAmberNormal)); + RegisterState(TLEDState.Create(FSXStateUIDGearDamageBySpeed, FSXStateDisplayNameGearDamageBySpeed, lcFlashingRedFast)); +end; + + +function TFSXGearFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXGearFunctionWorker; +end; + + +{ TFSXParkingBrakeFunction } +function TFSXParkingBrakeFunction.GetCategoryName: string; +begin + Result := FSXCategorySystems; +end; + +function TFSXParkingBrakeFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXParkingBrakeFunctionWorker; +end; + + +{ TFSXPressDumpSwitchFunction } +function TFSXPressDumpSwitchFunction.GetCategoryName: string; +begin + Result := FSXCategorySystems; +end; + +function TFSXPressDumpSwitchFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXPressDumpSwitchFunctionWorker; +end; + + +{ TFSXTailHookFunction } +procedure TFSXTailHookFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDTailHookRetracted, FSXStateDisplayNameTailHookRetracted, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDTailHookBetween, FSXStateDisplayNameTailHookBetween, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDTailHookExtended, FSXStateDisplayNameTailHookExtended, lcRed)); +end; + + +function TFSXTailHookFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXTailHookFunctionWorker; +end; + + +{ TFSXEngineAntiIceFunction } +function TFSXEngineAntiIceFunction.GetCategoryName: string; +begin + Result := FSXCategoryEngines; +end; + + +procedure TFSXEngineAntiIceFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDEngineAntiIceNoEngines, FSXStateDisplayNameEngineAntiIceNoEngines, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDEngineAntiIceAll, FSXStateDisplayNameEngineAntiIceAll, lcRed)); + RegisterState(TLEDState.Create(FSXStateUIDEngineAntiIcePartial, FSXStateDisplayNameEngineAntiIcePartial, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDEngineAntiIceNone, FSXStateDisplayNameEngineAntiIceNone, lcGreen)); +end; + + +function TFSXEngineAntiIceFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXEngineAntiIceFunctionWorker; +end; + + +{ TFSXEngineFunction } +function TFSXEngineFunction.GetCategoryName: string; +begin + Result := FSXCategoryEngines; +end; + + +procedure TFSXEngineFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDEngineNoEngines, FSXStateDisplayNameEngineNoEngines, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDEngineAllRunning, FSXStateDisplayNameEngineAllRunning, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDEnginePartiallyRunning, FSXStateDisplayNameEnginePartiallyRunning, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDEngineAllOff, FSXStateDisplayNameEngineAllOff, lcRed)); + RegisterState(TLEDState.Create(FSXStateUIDEngineFailed, FSXStateDisplayNameEngineFailed, lcFlashingRedNormal)); + RegisterState(TLEDState.Create(FSXStateUIDEngineOnFire, FSXStateDisplayNameEngineOnFire, lcFlashingRedFast)); +end; + + +function TFSXEngineFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXEngineFunctionWorker; +end; + + +{ TFSXFlapsFunction } +function TFSXFlapsFunction.GetCategoryName: string; +begin + Result := FSXCategoryControlSurfaces; +end; + + +procedure TFSXFlapsFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDFlapsNotAvailable, FSXStateDisplayNameFlapsNotAvailable, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDFlapsRetracted, FSXStateDisplayNameFlapsRetracted, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDFlapsBetween, FSXStateDisplayNameFlapsBetween, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDFlapsExtended, FSXStateDisplayNameFlapsExtended, lcRed)); + RegisterState(TLEDState.Create(FSXStateUIDFlapsSpeedExceeded, FSXStateDisplayNameFlapsSpeedExceeded, lcFlashingAmberNormal)); + RegisterState(TLEDState.Create(FSXStateUIDFlapsDamageBySpeed, FSXStateDisplayNameFlapsDamageBySpeed, lcFlashingRedFast)); +end; + + +function TFSXFlapsFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXFlapsFunctionWorker; +end; + + +{ TFSXSpoilersFunction } +function TFSXSpoilersFunction.GetCategoryName: string; +begin + Result := FSXCategoryControlSurfaces; +end; + + +procedure TFSXSpoilersFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDSpoilersNotAvailable, FSXStateDisplayNameSpoilersNotAvailable, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDSpoilersRetracted, FSXStateDisplayNameSpoilersRetracted, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDSpoilersBetween, FSXStateDisplayNameSpoilersBetween, lcAmber)); + RegisterState(TLEDState.Create(FSXStateUIDSpoilersExtended, FSXStateDisplayNameSpoilersExtended, lcRed)); +end; + + +function TFSXSpoilersFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXSpoilersFunctionWorker; +end; + + +{ TFSXLightFunction } +function TCustomFSXLightFunction.GetCategoryName: string; +begin + Result := FSXCategoryLights; +end; + + +function TCustomFSXLightFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXLightStatesFunctionWorker; +end; + + +function TCustomFSXLightFunction.DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string): TCustomLEDFunctionWorker; +begin + Result := inherited DoCreateWorker(ASettings, APreviousState); + (Result as TFSXLightStatesFunctionWorker).StateMask := GetLightMask; +end; + + +{ TFSXLandingLightsFunction } +function TFSXLandingLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_LANDING; +end; + + +{ TFSXInstrumentLightsFunction } +function TFSXInstrumentLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_PANEL; +end; + + +{ TFSXBeaconLightsFunction } +function TFSXBeaconLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_BEACON; +end; + + +{ TFSXNavLightsFunction } +function TFSXNavLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_NAV; +end; + + +{ TFSXStrobeLightsFunction } +function TFSXStrobeLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_STROBE; +end; + + +{ TFSXTaxiLightsFunction } +function TFSXTaxiLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_TAXI; +end; + + +{ TFSXRecognitionLightsFunction } +function TFSXRecognitionLightsFunction.GetLightMask: Integer; +begin + Result := FSX_LIGHTON_RECOGNITION; +end; + + +{ TCustomFSXAutoPilotFunction } +function TCustomFSXAutoPilotFunction.GetCategoryName: string; +begin + Result := FSXCategoryAutoPilot; +end; + + +procedure TCustomFSXAutoPilotFunction.RegisterStates; +begin + RegisterState(TLEDState.Create(FSXStateUIDAutoPilotNotAvailable, FSXStateDisplayNameAutoPilotNotAvailable, lcOff)); + RegisterState(TLEDState.Create(FSXStateUIDOn, FSXStateDisplayNameOn, lcGreen)); + RegisterState(TLEDState.Create(FSXStateUIDOff, FSXStateDisplayNameOff, lcOff)); +end; + + +{ TFSXAutoPilotFunction } +function TFSXAutoPilotFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotFunctionWorker; +end; + + +{ TFSXAutoPilotHeadingFunction } +function TFSXAutoPilotHeadingFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotHeadingFunctionWorker; +end; + + +{ TFSXAutoPilotApproachFunction } +function TFSXAutoPilotApproachFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotApproachFunctionWorker; +end; + + +{ TFSXAutoPilotBackcourseFunction } +function TFSXAutoPilotBackcourseFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotBackcourseFunctionWorker; +end; + + +{ TFSXAutoPilotAltitudeFunction } +function TFSXAutoPilotAltitudeFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotAltitudeFunctionWorker; +end; + + +{ TFSXAutoPilotNavFunction } +function TFSXAutoPilotNavFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAutoPilotNavFunctionWorker; +end; + + +{ TFSXAvionicsMasterFunction } +function TFSXAvionicsMasterFunction.GetCategoryName: string; +begin + Result := FSXCategoryRadios; +end; + + +function TFSXAvionicsMasterFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; +begin + Result := TFSXAvionicsMasterFunctionWorker; +end; + +end. diff --git a/G940LEDControl/Units/FSXLEDFunctionProvider.pas b/G940LEDControl/Units/FSXLEDFunctionProvider.pas new file mode 100644 index 0000000..84ea46a --- /dev/null +++ b/G940LEDControl/Units/FSXLEDFunctionProvider.pas @@ -0,0 +1,293 @@ +unit FSXLEDFunctionProvider; + +interface +uses + Generics.Collections, + System.SyncObjs, + + FSXSimConnectIntf, + LEDFunction, + LEDFunctionIntf, + LEDStateIntf; + + +type + TCustomFSXFunction = class; + TCustomFSXFunctionList = TObjectList; + + + TFSXLEDFunctionProvider = class(TCustomLEDFunctionProvider, IFSXSimConnectObserver) + private + FSimConnect: TInterfacedObject; + FSimConnectLock: TCriticalSection; + protected + procedure RegisterFunctions; override; + + function GetUID: string; override; + protected + { IFSXSimConnectObserver } + procedure ObserveDestroy(Sender: IFSXSimConnect); + public + constructor Create; + destructor Destroy; override; + + function GetSimConnect: IFSXSimConnect; + end; + + + TCustomFSXFunction = class(TCustomMultiStateLEDFunction) + private + FProvider: TFSXLEDFunctionProvider; + FDisplayName: string; + FUID: string; + protected + function DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): TCustomLEDFunctionWorker; override; + + property Provider: TFSXLEDFunctionProvider read FProvider; + protected + function GetCategoryName: string; override; + function GetDisplayName: string; override; + function GetUID: string; override; + public + constructor Create(AProvider: TFSXLEDFunctionProvider; const ADisplayName, AUID: string); + end; + + + TCustomFSXFunctionClass = class of TCustomFSXFunction; + + + TCustomFSXFunctionWorker = class(TCustomLEDMultiStateFunctionWorker) + private + FDataHandler: IFSXSimConnectDataHandler; + FDefinitionID: Cardinal; + FSimConnect: IFSXSimConnect; + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); virtual; abstract; + + procedure SetSimConnect(const Value: IFSXSimConnect); virtual; + + property DataHandler: IFSXSimConnectDataHandler read FDataHandler; + property DefinitionID: Cardinal read FDefinitionID; + property SimConnect: IFSXSimConnect read FSimConnect write SetSimConnect; + protected + procedure HandleData(AData: Pointer); virtual; abstract; + public + constructor Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); override; + destructor Destroy; override; + end; + + +implementation +uses + System.SysUtils, + + FSXLEDFunction, + FSXResources, + FSXSimConnectClient, + LEDFunctionRegistry, + SimConnect; + + +type + TCustomFSXFunctionWorkerDataHandler = class(TInterfacedObject, IFSXSimConnectDataHandler) + private + FWorker: TCustomFSXFunctionWorker; + protected + { IFSXSimConnectDataHandler } + procedure HandleData(AData: Pointer); + + property Worker: TCustomFSXFunctionWorker read FWorker; + public + constructor Create(AWorker: TCustomFSXFunctionWorker); + end; + + + +{ TFSXLEDFunctionProvider } +constructor TFSXLEDFunctionProvider.Create; +begin + inherited Create; + + FSimConnectLock := TCriticalSection.Create; +end; + + +destructor TFSXLEDFunctionProvider.Destroy; +begin + FreeAndNil(FSimConnectLock); + + inherited Destroy; +end; + + +procedure TFSXLEDFunctionProvider.RegisterFunctions; +begin + { Systems } + RegisterFunction(TFSXBatteryMasterFunction.Create( Self, FSXFunctionDisplayNameBatteryMaster, FSXFunctionUIDBatteryMaster)); + RegisterFunction(TFSXDeIceFunction.Create( Self, FSXFunctionDisplayNameDeIce, FSXFunctionUIDDeIce)); + RegisterFunction(TFSXExitDoorFunction.Create( Self, FSXFunctionDisplayNameExitDoor, FSXFunctionUIDExitDoor)); + RegisterFunction(TFSXGearFunction.Create( Self, FSXFunctionDisplayNameGear, FSXFunctionUIDGear)); + RegisterFunction(TFSXParkingBrakeFunction.Create( Self, FSXFunctionDisplayNameParkingBrake, FSXFunctionUIDParkingBrake)); + RegisterFunction(TFSXPressDumpSwitchFunction.Create( Self, FSXFunctionDisplayNamePressDumpSwitch, FSXFunctionUIDPressDumpSwitch)); + RegisterFunction(TFSXTailHookFunction.Create( Self, FSXFunctionDisplayNameTailHook, FSXFunctionUIDTailHook)); + + { Engines } + RegisterFunction(TFSXEngineAntiIceFunction.Create( Self, FSXFunctionDisplayNameEngineAntiIce, FSXFunctionUIDEngineAntiIce)); + RegisterFunction(TFSXEngineFunction.Create( Self, FSXFunctionDisplayNameEngine, FSXFunctionUIDEngine)); + + { Control surfaces } + RegisterFunction(TFSXFlapsFunction.Create( Self, FSXFunctionDisplayNameFlaps, FSXFunctionUIDFlaps)); + RegisterFunction(TFSXSpoilersFunction.Create( Self, FSXFunctionDisplayNameSpoilers, FSXFunctionUIDSpoilers)); + + { Lights } + RegisterFunction(TFSXBeaconLightsFunction.Create( Self, FSXFunctionDisplayNameBeaconLights, FSXFunctionUIDBeaconLights)); + RegisterFunction(TFSXInstrumentLightsFunction.Create( Self, FSXFunctionDisplayNameInstrumentLights, FSXFunctionUIDInstrumentLights)); + RegisterFunction(TFSXLandingLightsFunction.Create( Self, FSXFunctionDisplayNameLandingLights, FSXFunctionUIDLandingLights)); + RegisterFunction(TFSXNavLightsFunction.Create( Self, FSXFunctionDisplayNameNavLights, FSXFunctionUIDNavLights)); + RegisterFunction(TFSXRecognitionLightsFunction.Create( Self, FSXFunctionDisplayNameRecognitionLights, FSXFunctionUIDRecognitionLights)); + RegisterFunction(TFSXStrobeLightsFunction.Create( Self, FSXFunctionDisplayNameStrobeLights, FSXFunctionUIDStrobeLights)); + RegisterFunction(TFSXTaxiLightsFunction.Create( Self, FSXFunctionDisplayNameTaxiLights, FSXFunctionUIDTaxiLights)); + + { Autopilot } + RegisterFunction(TFSXAutoPilotFunction.Create( Self, FSXFunctionDisplayNameAutoPilot, FSXFunctionUIDAutoPilot)); + RegisterFunction(TFSXAutoPilotAltitudeFunction.Create( Self, FSXFunctionDisplayNameAutoPilotAltitude, FSXFunctionUIDAutoPilotAltitude)); + RegisterFunction(TFSXAutoPilotApproachFunction.Create( Self, FSXFunctionDisplayNameAutoPilotApproach, FSXFunctionUIDAutoPilotApproach)); + RegisterFunction(TFSXAutoPilotBackcourseFunction.Create(Self, FSXFunctionDisplayNameAutoPilotBackcourse, FSXFunctionUIDAutoPilotBackcourse)); + RegisterFunction(TFSXAutoPilotHeadingFunction.Create( Self, FSXFunctionDisplayNameAutoPilotHeading, FSXFunctionUIDAutoPilotHeading)); + RegisterFunction(TFSXAutoPilotNavFunction.Create( Self, FSXFunctionDisplayNameAutoPilotNav, FSXFunctionUIDAutoPilotNav)); + + { Radios } + RegisterFunction(TFSXAvionicsMasterFunction.Create( Self, FSXFunctionDisplayNameAvionicsMaster, FSXFunctionUIDAvionicsMaster)); +end; + + +function TFSXLEDFunctionProvider.GetUID: string; +begin + Result := FSXProviderUID; +end; + + +procedure TFSXLEDFunctionProvider.ObserveDestroy(Sender: IFSXSimConnect); +begin + FSimConnectLock.Acquire; + try + FSimConnect := nil; + finally + FSimConnectLock.Release; + end; +end; + + +function TFSXLEDFunctionProvider.GetSimConnect: IFSXSimConnect; +begin + FSimConnectLock.Acquire; + try + if not Assigned(FSimConnect) then + begin + { Keep an object reference so we don't increment the reference count. + We'll know when it's gone through the ObserveDestroy. } + FSimConnect := TFSXSimConnectInterface.Create; + (FSimConnect as IFSXSimConnect).Attach(Self); + end; + + Result := (FSimConnect as IFSXSimConnect); + finally + FSimConnectLock.Release; + end; +end; + + +{ TCustomFSXFunction } +constructor TCustomFSXFunction.Create(AProvider: TFSXLEDFunctionProvider; const ADisplayName, AUID: string); +begin + inherited Create(AProvider.GetUID); + + FProvider := AProvider; + FDisplayName := ADisplayName; + FUID := AUID; +end; + + +function TCustomFSXFunction.DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string): TCustomLEDFunctionWorker; +begin + Result := inherited DoCreateWorker(ASettings, APreviousState); + + (Result as TCustomFSXFunctionWorker).SimConnect := Provider.GetSimConnect; +end; + + +function TCustomFSXFunction.GetCategoryName: string; +begin + Result := FSXCategory; +end; + + +function TCustomFSXFunction.GetDisplayName: string; +begin + Result := FDisplayName; +end; + + +function TCustomFSXFunction.GetUID: string; +begin + Result := FUID; +end; + + +{ TCustomFSXFunctionWorker } +constructor TCustomFSXFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string); +begin + { We can't pass ourselves as the Data Handler, as it would keep a reference to + this worker from the SimConnect interface. That'd mean the worker never + gets destroyed, and SimConnect never shuts down. Hence this proxy class. } + FDataHandler := TCustomFSXFunctionWorkerDataHandler.Create(Self); + + inherited Create(AProviderUID, AFunctionUID, AStates, ASettings, APreviousState); +end; + + +destructor TCustomFSXFunctionWorker.Destroy; +begin + if DefinitionID <> 0 then + SimConnect.RemoveDefinition(DefinitionID, DataHandler); + + inherited Destroy; +end; + + +procedure TCustomFSXFunctionWorker.SetSimConnect(const Value: IFSXSimConnect); +var + definition: IFSXSimConnectDefinition; + +begin + FSimConnect := Value; + + if Assigned(SimConnect) then + begin + definition := SimConnect.CreateDefinition; + RegisterVariables(definition); + + FDefinitionID := SimConnect.AddDefinition(definition, DataHandler); + end; +end; + + +{ TCustomFSXFunctionWorkerDataHandler } +constructor TCustomFSXFunctionWorkerDataHandler.Create(AWorker: TCustomFSXFunctionWorker); +begin + inherited Create; + + FWorker := AWorker; +end; + + +procedure TCustomFSXFunctionWorkerDataHandler.HandleData(AData: Pointer); +begin + Worker.HandleData(AData); +end; + + +initialization + TLEDFunctionRegistry.Register(TFSXLEDFunctionProvider.Create); + +end. diff --git a/G940LEDControl/Units/FSXLEDFunctionWorker.pas b/G940LEDControl/Units/FSXLEDFunctionWorker.pas new file mode 100644 index 0000000..634145a --- /dev/null +++ b/G940LEDControl/Units/FSXLEDFunctionWorker.pas @@ -0,0 +1,629 @@ +unit FSXLEDFunctionWorker; + +interface +uses + FSXLEDFunctionProvider, + FSXSimConnectIntf; + + +type + { Systems } + TFSXBatteryMasterFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXDeIceFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXExitDoorFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXGearFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXParkingBrakeFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXPressDumpSwitchFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXTailHookFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + + { Engines } + TFSXEngineAntiIceFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXEngineFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + + { Control surfaces } + TFSXFlapsFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + TFSXSpoilersFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + + { Lights } + TFSXLightStatesFunctionWorker = class(TCustomFSXFunctionWorker) + private + FStateMask: Integer; + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + public + property StateMask: Integer read FStateMask write FStateMask; + end; + + + { Autopilot } + PAutoPilotData = ^TAutoPilotData; + TAutoPilotData = packed record + AutoPilotAvailable: Cardinal; + AutoPilotMaster: Cardinal; + AutoPilotHeading: Cardinal; + AutoPilotApproach: Cardinal; + AutoPilotBackcourse: Cardinal; + AutoPilotAltitude: Cardinal; + AutoPilotNav: Cardinal; + end; + + + TCustomFSXAutoPilotFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + + procedure SetOnOffState(AState: Cardinal); virtual; + procedure HandleAutoPilotData(AData: PAutoPilotData); virtual; abstract; + end; + + + TFSXAutoPilotFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + TFSXAutoPilotHeadingFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + TFSXAutoPilotApproachFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + TFSXAutoPilotBackcourseFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + TFSXAutoPilotAltitudeFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + TFSXAutoPilotNavFunctionWorker = class(TCustomFSXAutoPilotFunctionWorker) + protected + procedure HandleAutoPilotData(AData: PAutoPilotData); override; + end; + + + { Radios } + TFSXAvionicsMasterFunctionWorker = class(TCustomFSXFunctionWorker) + protected + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; + procedure HandleData(AData: Pointer); override; + end; + + +implementation +uses + System.Math, + System.SysUtils, + + FSXResources, + LEDStateIntf, + SimConnect; + + +{ TFSXBatteryMasterFunctionWorker } +procedure TFSXBatteryMasterFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('ELECTRICAL MASTER BATTERY', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXBatteryMasterFunctionWorker.HandleData(AData: Pointer); +begin + if PCardinal(AData)^ <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TFSXDeIceFunctionWorker } +procedure TFSXDeIceFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('STRUCTURAL DEICE SWITCH', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXDeIceFunctionWorker.HandleData(AData: Pointer); +begin + if PCardinal(AData)^ <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TFSXExitDoorFunctionWorker } +procedure TFSXExitDoorFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('CANOPY OPEN', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); +end; + + +procedure TFSXExitDoorFunctionWorker.HandleData(AData: Pointer); +begin + case Trunc(PDouble(AData)^) of + 0..5: SetCurrentState(FSXStateUIDExitDoorClosed); + 95..100: SetCurrentState(FSXStateUIDExitDoorOpen); + else SetCurrentState(FSXStateUIDExitDoorBetween); + end; +end; + + +{ TFSXGearFunctionWorker } +procedure TFSXGearFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('IS GEAR RETRACTABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('GEAR TOTAL PCT EXTENDED', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); + ADefinition.AddVariable('GEAR DAMAGE BY SPEED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('GEAR SPEED EXCEEDED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXGearFunctionWorker.HandleData(AData: Pointer); +type + PGearData = ^TGearData; + TGearData = packed record + IsGearRetractable: Cardinal; + TotalPctExtended: Double; + DamageBySpeed: Integer; + SpeedExceeded: Integer; + end; + +var + gearData: PGearData; + +begin + gearData := AData; + + if gearData^.DamageBySpeed <> 0 then + SetCurrentState(FSXStateUIDGearDamageBySpeed) + + else if gearData^.SpeedExceeded <> 0 then + SetCurrentState(FSXStateUIDGearSpeedExceeded) + + else if gearData^.IsGearRetractable <> 0 then + begin + case Trunc(gearData ^.TotalPctExtended * 100) of + 0: SetCurrentState(FSXStateUIDGearRetracted); + 95..100: SetCurrentState(FSXStateUIDGearExtended); + else SetCurrentState(FSXStateUIDGearBetween); + end; + end else + SetCurrentState(FSXStateUIDGearNotRetractable); +end; + + +{ TFSXParkingBrakeFunctionWorker } +procedure TFSXParkingBrakeFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('BRAKE PARKING INDICATOR', FSX_UNIT_BOOL, SIMCONNECT_DATATYPE_INT32); +end; + + +procedure TFSXParkingBrakeFunctionWorker.HandleData(AData: Pointer); +begin + if PCardinal(AData)^ <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TFSXPressDumpSwitchFunctionWorker } +procedure TFSXPressDumpSwitchFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('PRESSURIZATION DUMP SWITCH', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXPressDumpSwitchFunctionWorker.HandleData(AData: Pointer); +begin + if PCardinal(AData)^ <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TFSXTailHookFunctionWorker } +procedure TFSXTailHookFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('TAILHOOK POSITION', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); +end; + + +procedure TFSXTailHookFunctionWorker.HandleData(AData: Pointer); +begin + case Trunc(PDouble(AData)^) of + 0..5: SetCurrentState(FSXStateUIDTailHookRetracted); + 95..100: SetCurrentState(FSXStateUIDTailHookBetween); + else SetCurrentState(FSXStateUIDTailHookExtended); + end; +end; + + +{ TFSXEngineAntiIceFunctionWorker } +procedure TFSXEngineAntiIceFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +var + engineIndex: Integer; + +begin + ADefinition.AddVariable('NUMBER OF ENGINES', FSX_UNIT_NUMBER, SIMCONNECT_DATAType_INT32); + + for engineIndex := 1 to FSX_MAX_ENGINES do + ADefinition.AddVariable(Format('ENG ANTI ICE:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXEngineAntiIceFunctionWorker.HandleData(AData: Pointer); +type + PAntiIceData = ^TAntiIceData; + TAntiIceData = packed record + NumberOfEngines: Integer; + EngineAntiIce: array[1..FSX_MAX_ENGINES] of Integer; + end; + +var + antiIceData: PAntiIceData; + engineCount: Integer; + antiIceCount: Integer; + engineIndex: Integer; + +begin + antiIceData := AData; + engineCount := Min(antiIceData^.NumberOfEngines, FSX_MAX_ENGINES); + antiIceCount := 0; + + for engineIndex := 1 to engineCount do + begin + if antiIceData^.EngineAntiIce[engineIndex] <> 0 then + Inc(antiIceCount); + end; + + if engineCount > 0 then + begin + if antiIceCount = 0 then + SetCurrentState(FSXStateUIDEngineAntiIceNone) + else if antiIceCount = engineCount then + SetCurrentState(FSXStateUIDEngineAntiIceAll) + else + SetCurrentState(FSXStateUIDEngineAntiIcePartial); + end else + SetCurrentState(FSXStateUIDEngineAntiIceNoEngines); +end; + + + +{ TFSXEngineFunctionWorker } +procedure TFSXEngineFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +var + engineIndex: Integer; + +begin + ADefinition.AddVariable('NUMBER OF ENGINES', FSX_UNIT_NUMBER, SIMCONNECT_DATAType_INT32); + + for engineIndex := 1 to FSX_MAX_ENGINES do + ADefinition.AddVariable(Format('GENERAL ENG COMBUSTION:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + + for engineIndex := 1 to FSX_MAX_ENGINES do + ADefinition.AddVariable(Format('ENG FAILED:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + + for engineIndex := 1 to FSX_MAX_ENGINES do + ADefinition.AddVariable(Format('ENG ON FIRE:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXEngineFunctionWorker.HandleData(AData: Pointer); +type + PEngineData = ^TEngineData; + TEngineData = packed record + NumberOfEngines: Integer; + Combustion: array[1..FSX_MAX_ENGINES] of Integer; + Failed: array[1..FSX_MAX_ENGINES] of Integer; + OnFire: array[1..FSX_MAX_ENGINES] of Integer; + end; + +var + engineData: PEngineData; + engineCount: Integer; + engineIndex: Integer; + hasFire: Boolean; + hasFailure: Boolean; + runningCount: Integer; + +begin + engineData := AData; + + if engineData^.NumberOfEngines > 0 then + begin + engineCount := Min(engineData^.NumberOfEngines, FSX_MAX_ENGINES); + hasFire := False; + hasFailure := False; + runningCount := 0; + + for engineIndex := 1 to engineCount do + begin + if engineData^.OnFire[engineIndex] <> 0 then + hasFire := True; + + if engineData^.Failed[engineIndex] <> 0 then + hasFailure := True; + + if engineData^.Combustion[engineIndex] <> 0 then + Inc(runningCount); + end; + + if hasFire then + SetCurrentState(FSXStateUIDEngineOnFire) + + else if hasFailure then + SetCurrentState(FSXStateUIDEngineFailed) + + else if runningCount = 0 then + SetCurrentState(FSXStateUIDEngineAllOff) + + else if runningCount = engineCount then + SetCurrentState(FSXStateUIDEngineAllRunning) + + else + SetCurrentState(FSXStateUIDEnginePartiallyRunning); + end else + SetCurrentState(FSXStateUIDEngineNoEngines); +end; + + +{ TFSXFlapsFunctionWorker } +procedure TFSXFlapsFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('FLAPS AVAILABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('FLAPS HANDLE PERCENT', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); + ADefinition.AddVariable('FLAP DAMAGE BY SPEED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('FLAP SPEED EXCEEDED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXFlapsFunctionWorker.HandleData(AData: Pointer); +type + PFlapsData = ^TFlapsData; + TFlapsData = packed record + FlapsAvailable: Cardinal; + FlapsHandlePercent: Double; + DamageBySpeed: Integer; + SpeedExceeded: Integer; + end; + +var + flapsData: PFlapsData; + +begin + flapsData := AData; + + if flapsData^.FlapsAvailable <> 0 then + begin + if flapsData^.DamageBySpeed <> 0 then + SetCurrentState(FSXStateUIDFlapsDamageBySpeed) + else if flapsData^.SpeedExceeded <> 0 then + SetCurrentState(FSXStateUIDFlapsSpeedExceeded) + else + case Trunc(flapsData^.FlapsHandlePercent) of + 0..5: SetCurrentState(FSXStateUIDFlapsRetracted); + 95..100: SetCurrentState(FSXStateUIDFlapsExtended); + else SetCurrentState(FSXStateUIDFlapsBetween); + end; + end else + SetCurrentState(FSXStateUIDFlapsNotAvailable); +end; + + +{ TFSXSpoilersFunctionWorker } +procedure TFSXSpoilersFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('SPOILER AVAILABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('SPOILERS HANDLE POSITION', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); +end; + + +procedure TFSXSpoilersFunctionWorker.HandleData(AData: Pointer); +type + PSpoilersData = ^TSpoilersData; + TSpoilersData = packed record + SpoilersAvailable: Cardinal; + SpoilersHandlePercent: Double; + end; + +var + spoilersData: PSpoilersData; + +begin + SpoilersData := AData; + + if SpoilersData^.SpoilersAvailable <> 0 then + begin + case Trunc(SpoilersData^.SpoilersHandlePercent) of + 0..5: SetCurrentState(FSXStateUIDSpoilersRetracted); + 95..100: SetCurrentState(FSXStateUIDSpoilersExtended); + else SetCurrentState(FSXStateUIDSpoilersBetween); + end; + end else + SetCurrentState(FSXStateUIDSpoilersNotAvailable); +end; + + +{ TFSXLightStatesFunctionWorker } +procedure TFSXLightStatesFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('LIGHT ON STATES', FSX_UNIT_MASK, SIMCONNECT_DATATYPE_INT32); +end; + + +procedure TFSXLightStatesFunctionWorker.HandleData(AData: Pointer); +begin + if (PCardinal(AData)^ and StateMask) <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TCustomFSXAutoPilotFunctionWorker } +procedure TCustomFSXAutoPilotFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('AUTOPILOT AVAILABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT MASTER', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT HEADING LOCK', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT APPROACH HOLD', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT BACKCOURSE HOLD', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT ALTITUDE LOCK', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('AUTOPILOT NAV1 LOCK', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TCustomFSXAutoPilotFunctionWorker.HandleData(AData: Pointer); +var + autoPilotData: PAutoPilotData; + +begin + autoPilotData := AData; + + if autoPilotData^.AutoPilotAvailable <> 0 then + HandleAutoPilotData(autoPilotData) + else + SetCurrentState(FSXStateUIDOff); +end; + + +procedure TCustomFSXAutoPilotFunctionWorker.SetOnOffState(AState: Cardinal); +begin + if AState <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + + +{ TFSXAutoPilotFunctionWorker } +procedure TFSXAutoPilotFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotMaster); +end; + + +{ TFSXAutoPilotHeadingFunctionWorker } +procedure TFSXAutoPilotHeadingFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotHeading); +end; + + +{ TFSXAutoPilotApproachFunctionWorker } +procedure TFSXAutoPilotApproachFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotApproach); +end; + + +{ TFSXAutoPilotBackcourseFunctionWorker } +procedure TFSXAutoPilotBackcourseFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotBackcourse); +end; + + +{ TFSXAutoPilotAltitudeFunctionWorker } +procedure TFSXAutoPilotAltitudeFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotAltitude); +end; + + +{ TFSXAutoPilotNavFunctionWorker } +procedure TFSXAutoPilotNavFunctionWorker.HandleAutoPilotData(AData: PAutoPilotData); +begin + SetOnOffState(AData^.AutoPilotNav); +end; + + +{ TFSXAvionicsMasterFunctionWorker } +procedure TFSXAvionicsMasterFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); +begin + ADefinition.AddVariable('AVIONICS MASTER SWITCH', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); +end; + + +procedure TFSXAvionicsMasterFunctionWorker.HandleData(AData: Pointer); +begin + if PCardinal(AData)^ <> 0 then + SetCurrentState(FSXStateUIDOn) + else + SetCurrentState(FSXStateUIDOff); +end; + +end. diff --git a/G940LEDControl/Units/FSXResources.pas b/G940LEDControl/Units/FSXResources.pas new file mode 100644 index 0000000..04eed5d --- /dev/null +++ b/G940LEDControl/Units/FSXResources.pas @@ -0,0 +1,196 @@ +unit FSXResources; + +interface +const + FSXSimConnectAppName = 'G940 LED Control'; + + FSXProviderUID = 'fsx'; + FSXCategory = 'Flight Simulator X'; + FSXCategorySystems = FSXCategory + ' - Systems'; + FSXCategoryEngines = FSXCategory + ' - Engines'; + FSXCategoryControlSurfaces = FSXCategory + ' - Control surfaces'; + FSXCategoryLights = FSXCategory + ' - Lights'; + FSXCategoryAutoPilot = FSXCategory + ' - Autopilot'; + FSXCategoryRadios = FSXCategory + ' - Radios'; + + FSXStateUIDOn = 'on'; + FSXStateUIDOff = 'off'; + + FSXStateDisplayNameOn = 'On'; + FSXStateDisplayNameOff = 'Off'; + + + FSXFunctionUIDEngine = 'engine'; + FSXFunctionDisplayNameEngine = 'Engine'; + + FSXStateUIDEngineNoEngines = 'noEngines'; + FSXStateUIDEngineAllRunning = 'allRunning'; + FSXStateUIDEnginePartiallyRunning = 'partiallyRunning'; + FSXStateUIDEngineAllOff = 'allOff'; + FSXStateUIDEngineFailed = 'failed'; + FSXStateUIDEngineOnFire = 'onFire'; + + FSXStateDisplayNameEngineNoEngines = 'No engines'; + FSXStateDisplayNameEngineAllRunning = 'All running'; + FSXStateDisplayNameEnginePartiallyRunning = 'Partially running'; + FSXStateDisplayNameEngineAllOff = 'All off'; + FSXStateDisplayNameEngineFailed = 'Engine failure'; + FSXStateDisplayNameEngineOnFire = 'On fire'; + + + FSXFunctionUIDGear = 'gear'; + FSXFunctionDisplayNameGear = 'Landing gear'; + + FSXStateUIDGearNotRetractable = 'notRetractable'; + FSXStateUIDGearRetracted = 'retracted'; + FSXStateUIDGearBetween = 'between'; + FSXStateUIDGearExtended = 'extended'; + FSXStateUIDGearSpeedExceeded = 'speedExceeded'; + FSXStateUIDGearDamageBySpeed = 'damageBySpeed'; + + FSXStateDisplayNameGearNotRetractable = 'Not retractable'; + FSXStateDisplayNameGearRetracted = 'Retracted'; + FSXStateDisplayNameGearBetween = 'Extending / retracting'; + FSXStateDisplayNameGearExtended = 'Extended'; + FSXStateDisplayNameGearSpeedExceeded = 'Speed exceeded'; + FSXStateDisplayNameGearDamageBySpeed = 'Damage by speed'; + + + FSXFunctionUIDLandingLights = 'landingLights'; + FSXFunctionDisplayNameLandingLights = 'Landing lights'; + + FSXFunctionUIDInstrumentLights = 'instrumentLights'; + FSXFunctionDisplayNameInstrumentLights = 'Instrument lights'; + + FSXFunctionUIDBeaconLights = 'beaconLights'; + FSXFunctionDisplayNameBeaconLights = 'Beacon lights'; + + FSXFunctionUIDNavLights = 'navLights'; + FSXFunctionDisplayNameNavLights = 'Nav lights'; + + FSXFunctionUIDStrobeLights = 'strobeLights'; + FSXFunctionDisplayNameStrobeLights = 'Strobe lights'; + + FSXFunctionUIDTaxiLights = 'taxiLights'; + FSXFunctionDisplayNameTaxiLights = 'Taxi lights'; + + FSXFunctionUIDRecognitionLights = 'recognitionLights'; + FSXFunctionDisplayNameRecognitionLights = 'Recognition lights'; + + + FSXFunctionUIDParkingBrake = 'parkingBrake'; + FSXFunctionDisplayNameParkingBrake = 'Parking brake'; + + + FSXFunctionUIDExitDoor = 'exitDoor'; + FSXFunctionDisplayNameExitDoor = 'Exit door'; + + FSXStateUIDExitDoorClosed = 'closed'; + FSXStateUIDExitDoorBetween = 'between'; + FSXStateUIDExitDoorOpen = 'open'; + + FSXStateDisplayNameExitDoorClosed = 'Closed'; + FSXStateDisplayNameExitDoorBetween = 'Opening / closing'; + FSXStateDisplayNameExitDoorOpen = 'Open'; + + + FSXFunctionUIDTailHook = 'tailHook'; + FSXFunctionDisplayNameTailHook = 'Tail hook'; + + FSXStateUIDTailHookRetracted = 'retracted'; + FSXStateUIDTailHookBetween = 'between'; + FSXStateUIDTailHookExtended = 'extended'; + + FSXStateDisplayNameTailHookRetracted = 'Retracted'; + FSXStateDisplayNameTailHookBetween = 'Extending / retracting'; + FSXStateDisplayNameTailHookExtended = 'Extended'; + + + FSXFunctionUIDFlaps = 'flaps'; + FSXFunctionDisplayNameFlaps = 'Flaps'; + + FSXStateUIDFlapsNotAvailable = 'notAvailable'; + FSXStateUIDFlapsRetracted = 'retracted'; + FSXStateUIDFlapsBetween = 'between'; + FSXStateUIDFlapsExtended = 'extended'; + FSXStateUIDFlapsSpeedExceeded = 'speedExceeded'; + FSXStateUIDFlapsDamageBySpeed = 'damageBySpeed'; + + FSXStateDisplayNameFlapsNotAvailable = 'No flaps'; + FSXStateDisplayNameFlapsRetracted = 'Retracted'; + FSXStateDisplayNameFlapsBetween = 'Extending / retracting'; + FSXStateDisplayNameFlapsExtended = 'Extended'; + FSXStateDisplayNameFlapsSpeedExceeded = 'Speed exceeded'; + FSXStateDisplayNameFlapsDamageBySpeed = 'Damage by speed'; + + + FSXFunctionUIDSpoilers = 'spoilers'; + FSXFunctionDisplayNameSpoilers = 'Spoilers'; + + FSXStateUIDSpoilersNotAvailable = 'notAvailable'; + FSXStateUIDSpoilersRetracted = 'retracted'; + FSXStateUIDSpoilersBetween = 'between'; + FSXStateUIDSpoilersExtended = 'extended'; + + FSXStateDisplayNameSpoilersNotAvailable = 'No spoilers'; + FSXStateDisplayNameSpoilersRetracted = 'Retracted'; + FSXStateDisplayNameSpoilersBetween = 'Extending / retracting'; + FSXStateDisplayNameSpoilersExtended = 'Extended'; + + + FSXFunctionUIDBatteryMaster = 'batteryMaster'; + FSXFunctionDisplayNameBatteryMaster = 'Battery master'; + + + FSXFunctionUIDAvionicsMaster = 'avionicsMaster'; + FSXFunctionDisplayNameAvionicsMaster = 'Avionics master'; + + + FSXFunctionUIDPressDumpSwitch = 'pressurizationDumpSwitch'; + FSXFunctionDisplayNamePressDumpSwitch = 'Pressurization dump switch'; + + FSXFunctionUIDEngineAntiIce = 'engineAntiIce'; + FSXFunctionDisplayNameEngineAntiIce = 'Engine anti-ice'; + + FSXStateUIDEngineAntiIceNoEngines = 'noEngines'; + FSXStateUIDEngineAntiIceAll = 'all'; + FSXStateUIDEngineAntiIcePartial = 'partial'; + FSXStateUIDEngineAntiIceNone = 'none'; + + FSXStateDisplayNameEngineAntiIceNoEngines = 'No engines'; + FSXStateDisplayNameEngineAntiIceAll = 'All'; + FSXStateDisplayNameEngineAntiIcePartial = 'Partial'; + FSXStateDisplayNameEngineAntiIceNone = 'None'; + + + FSXFunctionUIDDeIce = 'structuralDeIce'; + FSXFunctionDisplayNameDeIce = 'De-ice'; + + + FSXStateUIDAutoPilotNotAvailable = 'notAvailable'; + FSXStateDisplayNameAutoPilotNotAvailable = 'Not available'; + + FSXFunctionUIDAutoPilot = 'autoPilotMaster'; + FSXFunctionDisplayNameAutoPilot = 'Autopilot master'; + + FSXFunctionUIDAutoPilotHeading = 'autoPilotHeading'; + FSXFunctionDisplayNameAutoPilotHeading = 'Autopilot heading'; + + FSXFunctionUIDAutoPilotApproach = 'autoPilotApproach'; + FSXFunctionDisplayNameAutoPilotApproach = 'Autopilot approach'; + + FSXFunctionUIDAutoPilotBackcourse = 'autoPilotBackcourse'; + FSXFunctionDisplayNameAutoPilotBackcourse = 'Autopilot backcourse'; + + FSXFunctionUIDAutoPilotAltitude = 'autoPilotAltitude'; + FSXFunctionDisplayNameAutoPilotAltitude = 'Autopilot altitude'; + + FSXFunctionUIDAutoPilotNav = 'autoPilotNav'; + FSXFunctionDisplayNameAutoPilotNav = 'Autopilot nav'; + + + + +implementation + +end. diff --git a/G940LEDControl/Units/FSXSimConnectClient.pas b/G940LEDControl/Units/FSXSimConnectClient.pas new file mode 100644 index 0000000..25043a0 --- /dev/null +++ b/G940LEDControl/Units/FSXSimConnectClient.pas @@ -0,0 +1,665 @@ +unit FSXSimConnectClient; + +interface +uses + Classes, + + OtlTaskControl, + + FSXSimConnectIntf; + + +type + TFSXSimConnectInterface = class(TInterfacedObject, IFSXSimConnect) + private + FClient: IOmniTaskControl; + FObservers: TInterfaceList; + protected + property Client: IOmniTaskControl read FClient; + property Observers: TInterfaceList read FObservers; + protected + { IFSXSimConnect } + procedure Attach(AObserver: IFSXSimConnectObserver); + procedure Detach(AObserver: IFSXSimConnectObserver); + + function CreateDefinition: IFSXSimConnectDefinition; + function AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; + procedure RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); + public + constructor Create; + destructor Destroy; override; + end; + + +implementation +uses + Generics.Collections, + System.Math, + System.SyncObjs, + System.SysUtils, + Winapi.Windows, + + OtlComm, + OtlCommon, + SimConnect, + + FSXResources, + FSXSimConnectStateMonitor; + + +const + TM_ADDDEFINITION = 3001; + TM_REMOVEDEFINITION = 3002; + TM_TRYSIMCONNECT = 3003; + + TIMER_TRYSIMCONNECT = 201; + + INTERVAL_TRYSIMCONNECT = 5000; + + +type + TFSXSimConnectDefinitionRef = class(TObject) + private + FDefinition: IFSXSimConnectDefinitionAccess; + FDataHandlers: TInterfaceList; + protected + property DataHandlers: TInterfaceList read FDataHandlers; + public + constructor Create(ADefinition: IFSXSimConnectDefinitionAccess); + destructor Destroy; override; + + procedure Attach(ADataHandler: IFSXSimConnectDataHandler); + function Detach(ADataHandler: IFSXSimConnectDataHandler): Integer; + + procedure HandleData(AData: Pointer); + + property Definition: IFSXSimConnectDefinitionAccess read FDefinition; + end; + + + TFSXSimConnectDefinitionMap = class(TObjectDictionary) + public + constructor Create(ACapacity: Integer = 0); reintroduce; + end; + + TFSXSimConnectClient = class(TOmniWorker) + private + FDefinitions: TFSXSimConnectDefinitionMap; + FLastDefinitionID: Cardinal; + FSimConnectHandle: THandle; + FSimConnectDataEvent: TEvent; + protected + procedure TMAddDefinition(var Msg: TOmniMessage); message TM_ADDDEFINITION; + procedure TMRemoveDefinition(var Msg: TOmniMessage); message TM_REMOVEDEFINITION; + procedure TMTrySimConnect(var Msg: TOmniMessage); message TM_TRYSIMCONNECT; + + procedure HandleSimConnectDataEvent; + protected + function Initialize: Boolean; override; + procedure Cleanup; override; + + procedure TrySimConnect; + + procedure RegisterDefinitions; + procedure RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinitionAccess); + procedure UpdateDefinition(ADefinitionID: Cardinal); + procedure UnregisterDefinition(ADefinitionID: Cardinal); + + function SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinitionAccess): Boolean; + + property Definitions: TFSXSimConnectDefinitionMap read FDefinitions; + property LastDefinitionID: Cardinal read FLastDefinitionID; + property SimConnectHandle: THandle read FSimConnectHandle; + property SimConnectDataEvent: TEvent read FSimConnectDataEvent; + end; + + + TFSXSimConnectVariable = class(TInterfacedPersistent, IFSXSimConnectVariable) + private + FVariableName: string; + FUnitsName: string; + FDataType: SIMCONNECT_DATAType; + FEpsilon: Single; + protected + { IFSXSimConnectVariable } + function GetVariableName: string; + function GetUnitsName: string; + function GetDataType: SIMCONNECT_DATAType; + function GetEpsilon: Single; + public + constructor Create(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single); + end; + + + TFSXSimConnectVariableList = TObjectList; + + TFSXSimConnectDefinition = class(TInterfacedObject, IFSXSimConnectDefinition, IFSXSimConnectDefinitionAccess) + private + FSimConnect: IFSXSimConnect; + FVariables: TFSXSimConnectVariableList; + protected + property SimConnect: IFSXSimConnect read FSimConnect; + property Variables: TFSXSimConnectVariableList read FVariables; + protected + { IFSXSimConnectDefinition } + procedure AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single = 0); + + { IFSXSimConnectDefinitionAccess } + function GetVariableCount: Integer; + function GetVariable(AIndex: Integer): IFSXSimConnectVariable; + public + constructor Create; + destructor Destroy; override; + end; + + + + TAddDefinitionValue = class(TOmniWaitableValue) + private + FDataHandler: IFSXSimConnectDataHandler; + FDefinition: IFSXSimConnectDefinition; + FDefinitionID: Cardinal; + + procedure SetDefinitionID(const Value: Cardinal); + public + constructor Create(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler); + + property DataHandler: IFSXSimConnectDataHandler read FDataHandler; + property Definition: IFSXSimConnectDefinition read FDefinition; + + property DefinitionID: Cardinal read FDefinitionID write SetDefinitionID; + end; + + + TRemoveDefinitionValue = class(TOmniWaitableValue) + private + FDataHandler: IFSXSimConnectDataHandler; + FDefinitionID: Cardinal; + public + constructor Create(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); + + property DataHandler: IFSXSimConnectDataHandler read FDataHandler; + property DefinitionID: Cardinal read FDefinitionID; + end; + + +{ TFSXSimConnectInterface } +constructor TFSXSimConnectInterface.Create; +var + worker: IOmniWorker; + +begin + inherited Create; + + FObservers := TInterfaceList.Create; + + worker := TFSXSimConnectClient.Create; + FClient := CreateTask(worker).Run; +end; + + +destructor TFSXSimConnectInterface.Destroy; +var + observer: IInterface; + +begin + for observer in Observers do + (observer as IFSXSimConnectObserver).ObserveDestroy(Self); + + FreeAndNil(FObservers); + + FClient.Terminate; + FClient := nil; + + inherited Destroy; +end; + + +procedure TFSXSimConnectInterface.Attach(AObserver: IFSXSimConnectObserver); +begin + Observers.Add(AObserver as IFSXSimConnectObserver); +end; + + +procedure TFSXSimConnectInterface.Detach(AObserver: IFSXSimConnectObserver); +begin + Observers.Remove(AObserver as IFSXSimConnectObserver); +end; + + +function TFSXSimConnectInterface.CreateDefinition: IFSXSimConnectDefinition; +begin + Result := TFSXSimConnectDefinition.Create; +end; + + +function TFSXSimConnectInterface.AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; +var + addDefinition: TAddDefinitionValue; + +begin + addDefinition := TAddDefinitionValue.Create(ADefinition, ADataHandler); + Client.Comm.Send(TM_ADDDEFINITION, addDefinition); + + addDefinition.WaitFor(INFINITE); + Result := addDefinition.DefinitionID; +end; + + +procedure TFSXSimConnectInterface.RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); +var + removeDefinition: TRemoveDefinitionValue; + +begin + removeDefinition := TRemoveDefinitionValue.Create(ADefinitionID, ADataHandler); + Client.Comm.Send(TM_REMOVEDEFINITION, removeDefinition); + + removeDefinition.WaitFor(INFINITE); +end; + + + +{ TFSXSimConnectDefinition } +constructor TFSXSimConnectDefinition.Create; +begin + inherited Create; + + FVariables := TFSXSimConnectVariableList.Create(True); +end; + + +destructor TFSXSimConnectDefinition.Destroy; +begin + FreeAndNil(FVariables); + + inherited Destroy; +end; + + +procedure TFSXSimConnectDefinition.AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single); +begin + Variables.Add(TFSXSimConnectVariable.Create(AVariableName, AUnitsName, ADataType, AEpsilon)); +end; + + +function TFSXSimConnectDefinition.GetVariable(AIndex: Integer): IFSXSimConnectVariable; +begin + Result := Variables[AIndex]; +end; + + +function TFSXSimConnectDefinition.GetVariableCount: Integer; +begin + Result := Variables.Count; +end; + + +{ TFSXSimConnectClient } +function TFSXSimConnectClient.Initialize: Boolean; +begin + Result := inherited Initialize; + if not Result then + exit; + + FDefinitions := TFSXSimConnectDefinitionMap.Create; + FSimConnectDataEvent := TEvent.Create(nil, False, False, ''); + + Task.RegisterWaitObject(SimConnectDataEvent.Handle, HandleSimConnectDataEvent); + + TrySimConnect; +end; + + +procedure TFSXSimConnectClient.Cleanup; +begin + FreeAndNil(FSimConnectDataEvent); + FreeAndNil(FDefinitions); + + if SimConnectHandle <> 0 then + SimConnect_Close(SimConnectHandle); + + TFSXSimConnectStateMonitor.SetCurrentState(scsDisconnected); + + inherited Cleanup; +end; + + +procedure TFSXSimConnectClient.TrySimConnect; +begin + if SimConnectHandle <> 0 then + exit; + + if InitSimConnect then + begin + if SimConnect_Open(FSimConnectHandle, FSXSimConnectAppName, 0, 0, SimConnectDataEvent.Handle, 0) = S_OK then + begin + TFSXSimConnectStateMonitor.SetCurrentState(scsConnected); + + Task.ClearTimer(TIMER_TRYSIMCONNECT); + RegisterDefinitions; + end; + end; + + if SimConnectHandle = 0 then + begin + TFSXSimConnectStateMonitor.SetCurrentState(scsFailed); + + Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT); + end; +end; + + +procedure TFSXSimConnectClient.HandleSimConnectDataEvent; +var + data: PSimConnectRecv; + dataSize: Cardinal; + simObjectData: PSimConnectRecvSimObjectData; + definitionRef: TFSXSimConnectDefinitionRef; + +begin + while (SimConnectHandle <> 0) and + (SimConnect_GetNextDispatch(SimConnectHandle, data, dataSize) = S_OK) do + begin + case SIMCONNECT_RECV_ID(data^.dwID) of + SIMCONNECT_RECV_ID_SIMOBJECT_DATA: + begin + simObjectData := PSimConnectRecvSimObjectData(data); + + if Definitions.ContainsKey(simObjectData^.dwDefineID) then + begin + definitionRef := Definitions[simObjectData^.dwDefineID]; + definitionRef.HandleData(@simObjectData^.dwData); + end; + end; + + SIMCONNECT_RECV_ID_QUIT: + begin + FSimConnectHandle := 0; + Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT); + + TFSXSimConnectStateMonitor.SetCurrentState(scsDisconnected); + end; + end; + end; +end; + + +procedure TFSXSimConnectClient.RegisterDefinitions; +var + definitionID: Cardinal; + +begin + if SimConnectHandle = 0 then + exit; + + for definitionID in Definitions.Keys do + RegisterDefinition(definitionID, Definitions[definitionID].Definition); +end; + + +procedure TFSXSimConnectClient.RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinitionAccess); +var + variableIndex: Integer; + variable: IFSXSimConnectVariable; + +begin + if SimConnectHandle = 0 then + exit; + + for variableIndex := 0 to Pred(ADefinition.GetVariableCount) do + begin + variable := ADefinition.GetVariable(variableIndex); + SimConnect_AddToDataDefinition(SimConnectHandle, ADefinitionID, + AnsiString(variable.GetVariableName), + AnsiString(variable.GetUnitsName), + variable.GetDataType, + variable.GetEpsilon); + end; + + SimConnect_RequestDataOnSimObject(SimConnectHandle, ADefinitionID, ADefinitionID, + SIMCONNECT_OBJECT_ID_USER, + SIMCONNECT_PERIOD_SIM_FRAME, + SIMCONNECT_DATA_REQUEST_FLAG_CHANGED); +end; + + +procedure TFSXSimConnectClient.UpdateDefinition(ADefinitionID: Cardinal); +begin + if SimConnectHandle <> 0 then + { One-time data update; the RequestID is counted backwards to avoid conflicts with + the FLAG_CHANGED request which is still active } + SimConnect_RequestDataOnSimObject(SimConnectHandle, High(Cardinal) - ADefinitionID, ADefinitionID, + SIMCONNECT_OBJECT_ID_USER, + SIMCONNECT_PERIOD_SIM_FRAME, + 0, 0, 0, 1); +end; + + +procedure TFSXSimConnectClient.UnregisterDefinition(ADefinitionID: Cardinal); +begin + if SimConnectHandle <> 0 then + SimConnect_ClearDataDefinition(SimConnectHandle, ADefinitionID); +end; + + +function TFSXSimConnectClient.SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinitionAccess): Boolean; +var + variableIndex: Integer; + variable1: IFSXSimConnectVariable; + variable2: IFSXSimConnectVariable; + +begin + if ADefinition1.GetVariableCount = ADefinition2.GetVariableCount then + begin + Result := True; + + { Order is very important in the definitions, as the Data Handler depends + on it to interpret the data. } + for variableIndex := 0 to Pred(ADefinition1.GetVariableCount) do + begin + variable1 := ADefinition1.GetVariable(variableIndex); + variable2 := ADefinition2.GetVariable(variableIndex); + + if (variable1.GetVariableName <> variable2.GetVariableName) or + (variable1.GetUnitsName <> variable2.GetUnitsName) or + (variable1.GetDataType <> variable2.GetDataType) or + (not SameValue(variable1.GetEpsilon, variable2.GetEpsilon, 0.00001)) then + begin + Result := False; + break; + end; + end; + end else + Result := False; +end; + + +procedure TFSXSimConnectClient.TMAddDefinition(var Msg: TOmniMessage); +var + addDefinition: TAddDefinitionValue; + definitionID: Cardinal; + definitionRef: TFSXSimConnectDefinitionRef; + definitionAccess: IFSXSimConnectDefinitionAccess; + hasDefinition: Boolean; + +begin + addDefinition := Msg.MsgData; + definitionAccess := (addDefinition.Definition as IFSXSimConnectDefinitionAccess); + hasDefinition := False; + + { Attempt to re-use existing definition to save on SimConnect traffic } + for definitionID in Definitions.Keys do + begin + definitionRef := Definitions[definitionID]; + + if SameDefinition(definitionRef.Definition, definitionAccess) then + begin + definitionRef.Attach(addDefinition.DataHandler); + addDefinition.DefinitionID := definitionID; + + { Request an update on the definition to update the new worker } + UpdateDefinition(definitionID); + + hasDefinition := True; + break; + end; + end; + + if not hasDefinition then + begin + { Add as new definition } + Inc(FLastDefinitionID); + + definitionRef := TFSXSimConnectDefinitionRef.Create(definitionAccess); + definitionRef.Attach(addDefinition.DataHandler); + + Definitions.Add(LastDefinitionID, definitionRef); + addDefinition.DefinitionID := LastDefinitionID; + + { Register with SimConnect } + RegisterDefinition(LastDefinitionID, definitionAccess); + end; +end; + + +procedure TFSXSimConnectClient.TMRemoveDefinition(var Msg: TOmniMessage); +var + removeDefinition: TRemoveDefinitionValue; + definitionRef: TFSXSimConnectDefinitionRef; + +begin + removeDefinition := Msg.MsgData; + + if Definitions.ContainsKey(removeDefinition.DefinitionID) then + begin + definitionRef := Definitions[removeDefinition.DefinitionID]; + if definitionRef.Detach(removeDefinition.DataHandler) = 0 then + begin + { Unregister with SimConnect } + UnregisterDefinition(removeDefinition.DefinitionID); + + Definitions.Remove(removeDefinition.DefinitionID); + end; + end; + + removeDefinition.Signal; +end; + + +procedure TFSXSimConnectClient.TMTrySimConnect(var Msg: TOmniMessage); +begin + TrySimConnect; +end; + + +{ TFSXSimConnectDefinitionRef } +constructor TFSXSimConnectDefinitionRef.Create(ADefinition: IFSXSimConnectDefinitionAccess); +begin + inherited Create; + + FDataHandlers := TInterfaceList.Create; + FDefinition := ADefinition; +end; + + +destructor TFSXSimConnectDefinitionRef.Destroy; +begin + FreeAndNil(FDataHandlers); + + inherited Destroy; +end; + + +procedure TFSXSimConnectDefinitionRef.HandleData(AData: Pointer); +var + dataHandler: IInterface; + +begin + for dataHandler in DataHandlers do + (dataHandler as IFSXSimConnectDataHandler).HandleData(AData); +end; + + +procedure TFSXSimConnectDefinitionRef.Attach(ADataHandler: IFSXSimConnectDataHandler); +begin + DataHandlers.Add(ADataHandler as IFSXSimConnectDataHandler); +end; + + +function TFSXSimConnectDefinitionRef.Detach(ADataHandler: IFSXSimConnectDataHandler): Integer; +begin + DataHandlers.Remove(ADataHandler as IFSXSimConnectDataHandler); + Result := DataHandlers.Count; +end; + + +{ TFSXSimConnectDefinitionMap } +constructor TFSXSimConnectDefinitionMap.Create(ACapacity: Integer); +begin + inherited Create([doOwnsValues], ACapacity); +end; + + +{ TFSXSimConnectVariable } +constructor TFSXSimConnectVariable.Create(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single); +begin + inherited Create; + + FVariableName := AVariableName; + FUnitsName := AUnitsName; + FDataType := ADataType; + FEpsilon := AEpsilon; +end; + + +function TFSXSimConnectVariable.GetVariableName: string; +begin + Result := FVariableName; +end; + + +function TFSXSimConnectVariable.GetUnitsName: string; +begin + Result := FUnitsName; +end; + + +function TFSXSimConnectVariable.GetDataType: SIMCONNECT_DATAType; +begin + Result := FDataType; +end; + + +function TFSXSimConnectVariable.GetEpsilon: Single; +begin + Result := FEpsilon; +end; + + +{ TAddDefinitionValue } +constructor TAddDefinitionValue.Create(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler); +begin + inherited Create; + + FDefinition := ADefinition; + FDataHandler := ADataHandler; +end; + + +procedure TAddDefinitionValue.SetDefinitionID(const Value: Cardinal); +begin + FDefinitionID := Value; + Signal; +end; + + +{ TRemoveDefinitionValue } +constructor TRemoveDefinitionValue.Create(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); +begin + inherited Create; + + FDefinitionID := ADefinitionID; + FDataHandler := ADataHandler; +end; + +end. diff --git a/G940LEDControl/Units/FSXSimConnectIntf.pas b/G940LEDControl/Units/FSXSimConnectIntf.pas new file mode 100644 index 0000000..5f235a9 --- /dev/null +++ b/G940LEDControl/Units/FSXSimConnectIntf.pas @@ -0,0 +1,86 @@ +unit FSXSimConnectIntf; + +interface +uses + SimConnect; + + +type + IFSXSimConnect = interface; + IFSXSimConnectDefinition = interface; + + + IFSXSimConnectObserver = interface + ['{ACE8979A-D656-4F97-A332-A54BB615C4D1}'] + procedure ObserveDestroy(Sender: IFSXSimConnect); + end; + + + IFSXSimConnectDataHandler = interface + ['{29F00FB8-00AB-419F-83A3-A6AB3582599F}'] + procedure HandleData(AData: Pointer); + end; + + + IFSXSimConnect = interface + ['{B6BE3E7C-0804-43D6-84DE-8010C5728A07}'] + procedure Attach(AObserver: IFSXSimConnectObserver); + procedure Detach(AObserver: IFSXSimConnectObserver); + + function CreateDefinition: IFSXSimConnectDefinition; + function AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; + procedure RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); + end; + + + IFSXSimConnectDefinition = interface + ['{F1EAB3B1-0A3D-4B06-A75F-823E15C313B8}'] + procedure AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single = 0); + end; + + + IFSXSimConnectVariable = interface + ['{A41AD003-77C0-4E34-91E3-B0BAADD08FCE}'] + function GetVariableName: string; + function GetUnitsName: string; + function GetDataType: SIMCONNECT_DATAType; + function GetEpsilon: Single; + end; + + + IFSXSimConnectDefinitionAccess = interface + ['{2592534C-0344-4442-8A5F-1AB34B96E1B5}'] + function GetVariableCount: Integer; + function GetVariable(AIndex: Integer): IFSXSimConnectVariable; + end; + + + TFSXSimConnectState = (scsDisconnected, scsConnected, scsFailed); + + IFSXSimConnectStateObserver = interface + ['{0508904F-8189-479D-AF70-E98B00C9D9B2}'] + procedure ObserverStateUpdate(ANewState: TFSXSimConnectState); + end; + + +const + FSX_UNIT_PERCENT = 'percent'; + FSX_UNIT_MASK = 'mask'; + FSX_UNIT_BOOL = 'bool'; + FSX_UNIT_NUMBER = 'number'; + + FSX_LIGHTON_NAV = $0001; + FSX_LIGHTON_BEACON = $0002; + FSX_LIGHTON_LANDING = $0004; + FSX_LIGHTON_TAXI = $0008; + FSX_LIGHTON_STROBE = $0010; + FSX_LIGHTON_PANEL = $0020; + FSX_LIGHTON_RECOGNITION = $0040; + FSX_LIGHTON_CABIN = $0200; + + FSX_MAX_ENGINES = 4; + + +implementation + +end. diff --git a/G940LEDControl/Units/FSXSimConnectStateMonitor.pas b/G940LEDControl/Units/FSXSimConnectStateMonitor.pas new file mode 100644 index 0000000..60e9aa4 --- /dev/null +++ b/G940LEDControl/Units/FSXSimConnectStateMonitor.pas @@ -0,0 +1,114 @@ +unit FSXSimConnectStateMonitor; + +interface +uses + System.Classes, + System.SyncObjs, + + FSXSimConnectIntf; + + +type + TFSXSimConnectStateMonitor = class(TObject) + private + FObservers: TInterfaceList; + FCurrentStateLock: TCriticalSection; + FCurrentState: TFSXSimConnectState; + + procedure DoSetCurrentState(const Value: TFSXSimConnectState); + protected + property CurrentStateLock: TCriticalSection read FCurrentStateLock; + property Observers: TInterfaceList read FObservers; + public + constructor Create; + destructor Destroy; override; + + class function Instance: TFSXSimConnectStateMonitor; + class procedure SetCurrentState(AState: TFSXSimConnectState); + + procedure Attach(AObserver: IFSXSimConnectStateObserver); + procedure Detach(AObserver: IFSXSimConnectStateObserver); + + property CurrentState: TFSXSimConnectState read FCurrentState write DoSetCurrentState; + end; + + +implementation +uses + System.SysUtils; + + +var + FSXSimConnectStateInstance: TFSXSimConnectStateMonitor; + + +{ TFSXSimConnectState } +class function TFSXSimConnectStateMonitor.Instance: TFSXSimConnectStateMonitor; +begin + Result := FSXSimConnectStateInstance; +end; + + +class procedure TFSXSimConnectStateMonitor.SetCurrentState(AState: TFSXSimConnectState); +begin + Instance.DoSetCurrentState(AState); +end; + + +constructor TFSXSimConnectStateMonitor.Create; +begin + inherited Create; + + FCurrentStateLock := TCriticalSection.Create; + FObservers := TInterfaceList.Create; +end; + + +destructor TFSXSimConnectStateMonitor.Destroy; +begin + FreeAndNil(FObservers); + FreeAndNil(FCurrentStateLock); + + inherited Destroy; +end; + + +procedure TFSXSimConnectStateMonitor.Attach(AObserver: IFSXSimConnectStateObserver); +begin + Observers.Add(AObserver as IFSXSimConnectStateObserver); +end; + + +procedure TFSXSimConnectStateMonitor.Detach(AObserver: IFSXSimConnectStateObserver); +begin + Observers.Remove(AObserver as IFSXSimConnectStateObserver); +end; + + +procedure TFSXSimConnectStateMonitor.DoSetCurrentState(const Value: TFSXSimConnectState); +var + observer: IInterface; + +begin + CurrentStateLock.Acquire; + try + if Value <> FCurrentState then + begin + FCurrentState := Value; + + for observer in Observers do + (observer as IFSXSimConnectStateObserver).ObserverStateUpdate(CurrentState); + end; + finally + CurrentStateLock.Release; + end; +end; + + +initialization + FSXSimConnectStateInstance := TFSXSimConnectStateMonitor.Create; + +finalization + FreeAndNil(FSXSimConnectStateInstance); + +end. diff --git a/G940LEDControl/Units/G940LEDStateConsumer.pas b/G940LEDControl/Units/G940LEDStateConsumer.pas index 8739416..f668616 100644 --- a/G940LEDControl/Units/G940LEDStateConsumer.pas +++ b/G940LEDControl/Units/G940LEDStateConsumer.pas @@ -8,48 +8,39 @@ uses OtlComm, OtlTaskControl, - LEDFunctionMap, - LEDStateConsumer, - LEDStateProvider; + LEDStateConsumer; const - MSG_FINDTHROTTLEDEVICE = MSG_CONSUMER_OFFSET + 1; - MSG_NOTIFY_DEVICESTATE = MSG_CONSUMER_OFFSET + 2; - MSG_TIMER_BLINK = MSG_CONSUMER_OFFSET + 3; + TM_FINDTHROTTLEDEVICE = 2001; + TM_TESTTHROTTLEDEVICE = 2002; + + TM_NOTIFY_DEVICESTATE = 2003; - TIMER_BLINK = TIMER_CONSUMER_OFFSET + 1; type TG940LEDStateConsumer = class(TLEDStateConsumer) private FDirectInput: IDirectInput8; FThrottleDevice: IDirectInputDevice8; - - FRed: Byte; - FGreen: Byte; - - FBlinkTimerStarted: Boolean; - FBlinkCounter: Integer; + FTHrottleDeviceGUID: TGUID; protected - procedure MsgFindThrottleDevice(var msg: TOmniMessage); message MSG_FINDTHROTTLEDEVICE; - procedure MsgTimerBlink(var msg: TOmniMessage); message MSG_TIMER_BLINK; + procedure TMFindThrottleDevice(var Msg: TOmniMessage); message TM_FINDTHROTTLEDEVICE; + procedure TMTestThrottleDevice(var Msg: TOmniMessage); message TM_TESTTHROTTLEDEVICE; protected function Initialize: Boolean; override; - procedure ResetLEDState; override; - procedure LEDStateChanged(ALEDIndex: Integer; AState: TLEDState); override; - procedure Changed; override; - - procedure StartBlinkTimer; - procedure StopBlinkTimer; + procedure Cleanup; override; procedure FindThrottleDevice; procedure FoundThrottleDevice(ADeviceGUID: TGUID); procedure SetDeviceState(AState: Integer); + procedure Update; override; + property DirectInput: IDirectInput8 read FDirectInput; property ThrottleDevice: IDirectInputDevice8 read FThrottleDevice; + property ThrottleDeviceGUID: TGUID read FTHrottleDeviceGUID; end; @@ -58,8 +49,8 @@ const DEVICESTATE_FOUND = 1; DEVICESTATE_NOTFOUND = 2; - EXIT_ERROR_LOGIJOYSTICKDLL = EXIT_CONSUMER_OFFSET + 1; - EXIT_ERROR_DIRECTINPUT = EXIT_CONSUMER_OFFSET + 2; + EXIT_ERROR_LOGIJOYSTICKDLL = 9001; + EXIT_ERROR_DIRECTINPUT = 9002; implementation @@ -68,27 +59,14 @@ uses Windows, OtlCommon, + OtlTask, + LEDColorIntf, LogiJoystickDLL; - const - BLINK_INTERVAL = 500; - - -type - TRunInMainThreadSetLEDs = class(TOmniWaitableValue, IRunInMainThread) - private - FDevice: IDirectInputDevice8; - FRed: Byte; - FGreen: Byte; - protected - { IRunInMainThread } - procedure Execute; - public - constructor Create(ADevice: IDirectInputDevice8; ARed, AGreen: Byte); - end; + G940_BUTTONCOUNT = 8; function EnumDevicesProc(var lpddi: TDIDeviceInstanceW; pvRef: Pointer): BOOL; stdcall; @@ -134,101 +112,16 @@ begin end; Result := True; - Task.Comm.OtherEndpoint.Send(MSG_FINDTHROTTLEDEVICE); + Task.Comm.OtherEndpoint.Send(TM_FINDTHROTTLEDEVICE); end; -procedure TG940LEDStateConsumer.ResetLEDState; +procedure TG940LEDStateConsumer.Cleanup; begin - FRed := 0; - FGreen := $FF; - - inherited; -end; - - -procedure TG940LEDStateConsumer.LEDStateChanged(ALEDIndex: Integer; AState: TLEDState); - - procedure SetBit(var AMask: Byte; ABit: Integer; ASet: Boolean); inline; - begin - if ASet then - AMask := AMask or (1 shl ABit) - else - AMask := AMask and not (1 shl ABit); - end; - -var - red: Boolean; - green: Boolean; - -begin - red := False; - green := False; - - case AState of - lsGreen: - green := True; - - lsAmber: - begin - red := True; - green := True; - end; - - lsRed: - red := True; - - lsWarning: - begin - red := True; - green := True; - - StartBlinkTimer; - end; - - lsError: - begin - red := True; - - StartBlinkTimer; - end; - end; - - SetBit(FRed, ALEDIndex, red); - SetBit(FGreen, ALEDIndex, green); - - inherited; -end; - - -procedure TG940LEDStateConsumer.Changed; -begin - inherited; + inherited Cleanup; if Assigned(ThrottleDevice) then - { Logitech SDK will not change the color outside of the main thread } - RunInMainThread(TRunInMainThreadSetLEDs.Create(ThrottleDevice, FRed, FGreen), Destroying); -end; - - -procedure TG940LEDStateConsumer.StartBlinkTimer; -begin - if FBlinkTimerStarted then - exit; - - FBlinkCounter := 0; - Task.SetTimer(TIMER_BLINK, BLINK_INTERVAL, MSG_TIMER_BLINK); - FBlinkTimerStarted := True; -end; - - -procedure TG940LEDStateConsumer.StopBlinkTimer; -begin - if not FBlinkTimerStarted then - exit; - - Task.ClearTimer(TIMER_BLINK); - FBlinkTimerStarted := False; + SetLEDs(ThrottleDevice, 0, $FF); end; @@ -243,88 +136,89 @@ begin if not Assigned(ThrottleDevice) then SetDeviceState(DEVICESTATE_NOTFOUND) else - Changed; + Update; end; procedure TG940LEDStateConsumer.FoundThrottleDevice(ADeviceGUID: TGUID); begin if DirectInput.CreateDevice(ADeviceGUID, FThrottleDevice, nil) = S_OK then + begin + FTHrottleDeviceGUID := ADeviceGUID; SetDeviceState(DEVICESTATE_FOUND); + end; end; procedure TG940LEDStateConsumer.SetDeviceState(AState: Integer); begin - Task.Comm.Send(MSG_NOTIFY_DEVICESTATE, AState); + Task.Comm.Send(TM_NOTIFY_DEVICESTATE, AState); end; -procedure TG940LEDStateConsumer.MsgFindThrottleDevice(var msg: TOmniMessage); +procedure TG940LEDStateConsumer.Update; + + procedure SetBit(var AMask: Byte; ABit: Integer); inline; + begin + AMask := AMask or (1 shl ABit) + end; + + +var + red: Byte; + green: Byte; + buttonIndex: Integer; + buttonColor: TStaticLEDColor; + +begin + if not Assigned(ThrottleDevice) then + exit; + + red := 0; + green := 0; + + for buttonIndex := 0 to Pred(G940_BUTTONCOUNT) do + begin + if (buttonIndex >= ButtonColors.Count) or (not Assigned(ButtonColors[buttonIndex])) then + buttonColor := lcGreen + else + buttonColor := (ButtonColors[buttonIndex] as ILEDStateColor).GetCurrentColor; + + case buttonColor of + lcGreen: + SetBit(green, buttonIndex); + + lcAmber: + begin + SetBit(green, buttonIndex); + SetBit(red, buttonIndex); + end; + + lcRed: + SetBit(red, buttonIndex); + end; + end; + + SetLEDs(ThrottleDevice, red, green); +end; + + +procedure TG940LEDStateConsumer.TMFindThrottleDevice(var Msg: TOmniMessage); begin FindThrottleDevice; end; -procedure TG940LEDStateConsumer.MsgTimerBlink(var msg: TOmniMessage); -var - warningState: TLEDState; - errorState: TLEDState; - ledIndex: Integer; - state: TLEDState; - +procedure TG940LEDStateConsumer.TMTestThrottleDevice(var Msg: TOmniMessage); begin - Inc(FBlinkCounter); - if FBlinkCounter > 3 then - FBlinkCounter := 0; - - warningState := lsOff; - errorState := lsOff; - - { Error lights blink twice as fast } - if (FBlinkCounter in [0, 1]) then - warningState := lsAmber; - - if (FBlinkCounter in [0, 2]) then - errorState := lsRed; - - if StateMap.FindFirst([lsWarning, lsError], ledIndex, state) then + if Assigned(ThrottleDevice) then begin - BeginUpdate; - try - repeat - case state of - lsWarning: - if StateMap.GetState(ledIndex) <> warningState then - LEDStateChanged(ledIndex, warningState); - - lsError: - if StateMap.GetState(ledIndex) <> errorState then - LEDStateChanged(ledIndex, errorState); - end; - until not StateMap.FindNext([lsWarning, lsError], ledIndex, state); - finally - EndUpdate; + if DirectInput.GetDeviceStatus(ThrottleDeviceGUID) = DI_NOTATTACHED then + begin + FThrottleDevice := nil; + SetDeviceState(DEVICESTATE_NOTFOUND); end; - end else - StopBlinkTimer; -end; - - -{ TRunInMainThreadSetLEDs } -constructor TRunInMainThreadSetLEDs.Create(ADevice: IDirectInputDevice8; ARed, AGreen: Byte); -begin - inherited Create; - - FDevice := ADevice; - FRed := ARed; - FGreen := AGreen; -end; - - -procedure TRunInMainThreadSetLEDs.Execute; -begin - SetLEDs(FDevice, FRed, FGreen); + end; end; end. diff --git a/G940LEDControl/Units/LEDColor.pas b/G940LEDControl/Units/LEDColor.pas new file mode 100644 index 0000000..03f4b7a --- /dev/null +++ b/G940LEDControl/Units/LEDColor.pas @@ -0,0 +1,28 @@ +unit LEDColor; + +interface +uses + SysUtils, + + LEDColorIntf; + + +type + TCustomLEDStateColor = class(TInterfacedObject, ILEDStateColor) + protected + { ILEDState } + function GetCurrentColor: TStaticLEDColor; virtual; abstract; + end; + + + TCustomLEDStateDynamicColor = class(TCustomLEDStateColor, ILEDStateDynamicColor) + protected + { ITickLEDState } + procedure Reset; virtual; abstract; + procedure Tick; virtual; abstract; + end; + + +implementation + +end. diff --git a/G940LEDControl/Units/LEDColorIntf.pas b/G940LEDControl/Units/LEDColorIntf.pas new file mode 100644 index 0000000..17b415f --- /dev/null +++ b/G940LEDControl/Units/LEDColorIntf.pas @@ -0,0 +1,29 @@ +unit LEDColorIntf; + +interface +type + TLEDColor = (lcOff, lcGreen, lcAmber, lcRed, + lcFlashingGreenFast, lcFlashingGreenNormal, + lcFlashingAmberFast, lcFlashingAmberNormal, + lcFlashingRedFast, lcFlashingRedNormal); + + TStaticLEDColor = lcOff..lcRed; + + + + ILEDStateColor = interface + ['{B40DF462-B660-4002-A6B9-DD30AC69E8DB}'] + function GetCurrentColor: TStaticLEDColor; + end; + + + ILEDStateDynamicColor = interface(ILEDStateColor) + ['{9770E851-580D-4803-9979-0C608CB108A0}'] + procedure Reset; + procedure Tick; + end; + + +implementation + +end. diff --git a/G940LEDControl/Units/LEDColorPool.pas b/G940LEDControl/Units/LEDColorPool.pas new file mode 100644 index 0000000..03a337d --- /dev/null +++ b/G940LEDControl/Units/LEDColorPool.pas @@ -0,0 +1,89 @@ +unit LEDColorPool; + +interface +uses + LEDColorIntf; + + +type + TLEDColorPool = class(TObject) + private + FStates: array[TLEDColor] of ILEDStateColor; + protected + class function Instance: TLEDColorPool; + + function DoGetColor(AColor: TLEDColor): ILEDStateColor; + public + class function GetColor(AColor: TLEDColor): ILEDStateColor; overload; + end; + + +implementation +uses + SysUtils, + + DynamicLEDColor, + StaticLEDColor; + + +var + LEDColorPoolInstance: TLEDColorPool; + + +{ TLEDStatePool } +class function TLEDColorPool.GetColor(AColor: TLEDColor): ILEDStateColor; +begin + Result := Instance.DoGetColor(AColor); +end; + + +class function TLEDColorPool.Instance: TLEDColorPool; +begin + if not Assigned(LEDColorPoolInstance) then + LEDColorPoolInstance := TLEDColorPool.Create; + + Result := LEDColorPoolInstance; +end; + + +function TLEDColorPool.DoGetColor(AColor: TLEDColor): ILEDStateColor; + + function GetFlashingCycle(AColor: TLEDColor): TStaticLEDColorDynArray; + begin + SetLength(Result, 2); + Result[0] := AColor; + Result[1] := lcOff; + end; + +var + state: ILEDStateColor; + +begin + if not Assigned(FStates[AColor]) then + begin + case AColor of + lcOff: state := TLEDStateStaticColor.Create(lcOff); + lcGreen: state := TLEDStateStaticColor.Create(lcGreen); + lcAmber: state := TLEDStateStaticColor.Create(lcAmber); + lcRed: state := TLEDStateStaticColor.Create(lcRed); + + lcFlashingGreenFast: state := TDynamicLEDColor.Create(GetFlashingCycle(lcGreen), TICKINTERVAL_FAST); + lcFlashingGreenNormal: state := TDynamicLEDColor.Create(GetFlashingCycle(lcGreen), TICKINTERVAL_NORMAL); + lcFlashingAmberFast: state := TDynamicLEDColor.Create(GetFlashingCycle(lcAmber), TICKINTERVAL_FAST); + lcFlashingAmberNormal: state := TDynamicLEDColor.Create(GetFlashingCycle(lcAmber), TICKINTERVAL_NORMAL); + lcFlashingRedFast: state := TDynamicLEDColor.Create(GetFlashingCycle(lcRed), TICKINTERVAL_FAST); + lcFlashingRedNormal: state := TDynamicLEDColor.Create(GetFlashingCycle(lcRed), TICKINTERVAL_NORMAL); + end; + + FStates[AColor] := state; + Result := state; + end else + Result := FStates[AColor]; +end; + + +initialization +finalization + FreeAndNil(LEDColorPoolInstance); + +end. diff --git a/G940LEDControl/Units/LEDFunction.pas b/G940LEDControl/Units/LEDFunction.pas new file mode 100644 index 0000000..07ed8d3 --- /dev/null +++ b/G940LEDControl/Units/LEDFunction.pas @@ -0,0 +1,437 @@ +unit LEDFunction; + +interface +uses + System.Classes, + System.SyncObjs, + + LEDFunctionIntf, + LEDStateIntf; + + +type + TCustomLEDFunctionWorker = class; + TCustomLEDMultiStateFunctionWorkerClass = class of TCustomLEDMultiStateFunctionWorker; + + + TCustomLEDFunctionProvider = class(TInterfacedObject, ILEDFunctionProvider) + private + FFunctions: TInterfaceList; + protected + procedure RegisterFunctions; virtual; abstract; + function RegisterFunction(AFunction: ILEDFunction): ILEDFunction; virtual; + protected + { ILEDFunctionProvider } + function GetUID: string; virtual; abstract; + function GetEnumerator: ILEDFunctionEnumerator; virtual; + + function Find(const AFunctionUID: string): ILEDFunction; virtual; + public + constructor Create; + destructor Destroy; override; + end; + + + TCustomLEDFunction = class(TInterfacedObject, ILEDFunction) + protected + { ILEDFunction } + function GetCategoryName: string; virtual; abstract; + function GetDisplayName: string; virtual; abstract; + function GetUID: string; virtual; abstract; + + function CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): ILEDFunctionWorker; virtual; abstract; + end; + + + TCustomMultiStateLEDFunction = class(TCustomLEDFunction, ILEDMultiStateFunction) + private + FStates: TInterfaceList; + FProviderUID: string; + protected + procedure RegisterStates; virtual; abstract; + function RegisterState(AState: ILEDState): ILEDState; virtual; + + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; virtual; abstract; + function DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): TCustomLEDFunctionWorker; virtual; + protected + function CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): ILEDFunctionWorker; override; + + { ILEDMultiStateFunction } + function GetEnumerator: ILEDStateEnumerator; virtual; + public + constructor Create(const AProviderUID: string); + destructor Destroy; override; + end; + + + TCustomLEDFunctionWorker = class(TInterfacedObject, ILEDFunctionWorker) + private + FObservers: TInterfaceList; + FProviderUID: string; + FFunctionUID: string; + protected + procedure NotifyObservers; virtual; + + property Observers: TInterfaceList read FObservers; + protected + { ILEDFunctionWorker } + procedure Attach(AObserver: ILEDFunctionObserver); virtual; + procedure Detach(AObserver: ILEDFunctionObserver); virtual; + + function GetProviderUID: string; virtual; + function GetFunctionUID: string; virtual; + + function GetCurrentState: ILEDStateWorker; virtual; abstract; + public + constructor Create(const AProviderUID, AFunctionUID: string); + destructor Destroy; override; + end; + + + TCustomLEDMultiStateFunctionWorker = class(TCustomLEDFunctionWorker) + private + FStates: TInterfaceList; + FCurrentStateLock: TCriticalSection; + FCurrentState: ILEDStateWorker; + protected + procedure RegisterStates(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); virtual; + function FindState(const AUID: string): ILEDStateWorker; virtual; + + procedure SetCurrentState(const AUID: string; ANotifyObservers: Boolean = True); overload; virtual; + procedure SetCurrentState(AState: ILEDStateWorker; ANotifyObservers: Boolean = True); overload; virtual; + + property States: TInterfaceList read FStates; + protected + function GetCurrentState: ILEDStateWorker; override; + public + constructor Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); virtual; + destructor Destroy; override; + end; + + + TLEDFunctionEnumerator = class(TInterfacedObject, ILEDFunctionEnumerator) + private + FList: TInterfaceList; + FIndex: Integer; + protected + { ILEDFunctionEnumerator } + function GetCurrent: ILEDFunction; virtual; + function MoveNext: Boolean; virtual; + public + constructor Create(AList: TInterfaceList); + end; + + + TLEDStateEnumerator = class(TInterfacedObject, ILEDStateEnumerator) + private + FList: TInterfaceList; + FIndex: Integer; + protected + { ILEDStateEnumerator } + function GetCurrent: ILEDState; virtual; + function MoveNext: Boolean; virtual; + public + constructor Create(AList: TInterfaceList); + end; + + + +implementation +uses + System.SysUtils, + + LEDColorIntf, + LEDColorPool, + LEDState; + + +{ TCustomMultiStateLEDFunction } +constructor TCustomMultiStateLEDFunction.Create(const AProviderUID: string); +begin + inherited Create; + + FStates := TInterfaceList.Create; + FProviderUID := AProviderUID; + + RegisterStates; +end; + + +destructor TCustomMultiStateLEDFunction.Destroy; +begin + FreeAndNil(FStates); + + inherited Destroy; +end; + + +function TCustomMultiStateLEDFunction.RegisterState(AState: ILEDState): ILEDState; +begin + Result := AState as ILEDState; + FStates.Add(Result); +end; + + +function TCustomMultiStateLEDFunction.GetEnumerator: ILEDStateEnumerator; +begin + Result := TLEDStateEnumerator.Create(FStates); +end; + + +function TCustomMultiStateLEDFunction.CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string): ILEDFunctionWorker; +begin + Result := DoCreateWorker(ASettings, APreviousState); +end; + + +function TCustomMultiStateLEDFunction.DoCreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string): TCustomLEDFunctionWorker; +begin + Result := GetWorkerClass.Create(FProviderUID, GetUID, Self, ASettings, APreviousState); +end; + + +{ TCustomLEDFunctionWorker } +constructor TCustomLEDFunctionWorker.Create(const AProviderUID, AFunctionUID: string); +begin + inherited Create; + + FObservers := TInterfaceList.Create; + FProviderUID := AProviderUID; + FFunctionUID := AFunctionUID; +end; + + +destructor TCustomLEDFunctionWorker.Destroy; +begin + FreeAndNil(FObservers); + + inherited Destroy; +end; + + +procedure TCustomLEDFunctionWorker.Attach(AObserver: ILEDFunctionObserver); +begin + { TInterfaceList is thread-safe } + Observers.Add(AObserver as ILEDFunctionObserver); +end; + + +procedure TCustomLEDFunctionWorker.Detach(AObserver: ILEDFunctionObserver); +begin + Observers.Remove(AObserver as ILEDFunctionObserver); +end; + + +function TCustomLEDFunctionWorker.GetProviderUID: string; +begin + Result := FProviderUID; +end; + + +function TCustomLEDFunctionWorker.GetFunctionUID: string; +begin + Result := FFunctionUID; +end; + + +procedure TCustomLEDFunctionWorker.NotifyObservers; +var + observer: IInterface; + +begin + for observer in Observers do + (observer as ILEDFunctionObserver).ObserveUpdate(Self); +end; + + +{ TCustomLEDMultiStateFunctionWorker } +constructor TCustomLEDMultiStateFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string); +begin + inherited Create(AProviderUID, AFunctionUID); + + FCurrentStateLock := TCriticalSection.Create; + + FStates := TInterfaceList.Create; + RegisterStates(AStates, ASettings); + + if Length(APreviousState) > 0 then + FCurrentState := FindState(APreviousState); + + { Make sure we have a default state } + if (not Assigned(FCurrentState)) and (States.Count > 0) then + SetCurrentState((States[0] as ILEDStateWorker), False); +end; + + +destructor TCustomLEDMultiStateFunctionWorker.Destroy; +begin + FreeAndNil(FCurrentStateLock); + FreeAndNil(FStates); + + inherited Destroy; +end; + + +procedure TCustomLEDMultiStateFunctionWorker.RegisterStates(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); +var + state: ILEDState; + color: TLEDColor; + +begin + for state in AStates do + begin + if (not Assigned(ASettings)) or (not ASettings.GetStateColor(state.GetUID, color)) then + color := state.GetDefaultColor; + + States.Add(TLEDStateWorker.Create(state.GetUID, TLEDColorPool.GetColor(color))); + end; +end; + + +function TCustomLEDMultiStateFunctionWorker.FindState(const AUID: string): ILEDStateWorker; +var + state: IInterface; + +begin + Result := nil; + if not Assigned(States) then + exit; + + for state in States do + if (state as ICustomLEDState).GetUID = AUID then + begin + Result := (state as ILEDStateWorker); + break; + end; +end; + + +procedure TCustomLEDMultiStateFunctionWorker.SetCurrentState(const AUID: string; ANotifyObservers: Boolean); +begin + SetCurrentState(FindState(AUID), ANotifyObservers); +end; + + +procedure TCustomLEDMultiStateFunctionWorker.SetCurrentState(AState: ILEDStateWorker; ANotifyObservers: Boolean); +begin + FCurrentStateLock.Acquire; + try + if AState <> FCurrentState then + begin + FCurrentState := AState; + + if ANotifyObservers then + NotifyObservers; + end; + finally + FCurrentStateLock.Release; + end; +end; + + +function TCustomLEDMultiStateFunctionWorker.GetCurrentState: ILEDStateWorker; +begin + FCurrentStateLock.Acquire; + try + Result := FCurrentState; + finally + FCurrentStateLock.Release; + end; +end; + + +{ TCustomLEDFunctionProvider } +constructor TCustomLEDFunctionProvider.Create; +begin + inherited Create; + + FFunctions := TInterfaceList.Create; + RegisterFunctions; +end; + + +destructor TCustomLEDFunctionProvider.Destroy; +begin + FreeAndNil(FFunctions); + + inherited Destroy; +end; + + +function TCustomLEDFunctionProvider.Find(const AFunctionUID: string): ILEDFunction; +var + ledFunction: ILEDFunction; + +begin + Result := nil; + + for ledFunction in (Self as ILEDFunctionProvider) do + if ledFunction.GetUID = AFunctionUID then + begin + Result := ledFunction; + break; + end; +end; + + +function TCustomLEDFunctionProvider.RegisterFunction(AFunction: ILEDFunction): ILEDFunction; +begin + Result := AFunction as ILEDFunction; + FFunctions.Add(Result); +end; + + +function TCustomLEDFunctionProvider.GetEnumerator: ILEDFunctionEnumerator; +begin + Result := TLEDFunctionEnumerator.Create(FFunctions); +end; + + +{ TLEDFunctionEnumerator } +constructor TLEDFunctionEnumerator.Create(AList: TInterfaceList); +begin + inherited Create; + + FList := AList; + FIndex := -1; +end; + + +function TLEDFunctionEnumerator.GetCurrent: ILEDFunction; +begin + Result := (FList[FIndex] as ILEDFunction); +end; + + +function TLEDFunctionEnumerator.MoveNext: Boolean; +begin + Result := (FIndex < Pred(FList.Count)); + if Result then + Inc(FIndex); +end; + + +{ TLEDStateEnumerator } +constructor TLEDStateEnumerator.Create(AList: TInterfaceList); +begin + inherited Create; + + FList := AList; + FIndex := -1; +end; + + +function TLEDStateEnumerator.GetCurrent: ILEDState; +begin + Result := (FList[FIndex] as ILEDState); +end; + + +function TLEDStateEnumerator.MoveNext: Boolean; +begin + Result := (FIndex < Pred(FList.Count)); + if Result then + Inc(FIndex); +end; + +end. diff --git a/G940LEDControl/Units/LEDFunctionIntf.pas b/G940LEDControl/Units/LEDFunctionIntf.pas new file mode 100644 index 0000000..b4ce881 --- /dev/null +++ b/G940LEDControl/Units/LEDFunctionIntf.pas @@ -0,0 +1,86 @@ +unit LEDFunctionIntf; + +interface +uses + LEDColorIntf, + LEDStateIntf; + + +type + ILEDFunction = interface; + ILEDFunctionWorker = interface; + ILEDFunctionWorkerSettings = interface; + ILEDFunctionEnumerator = interface; + ILEDStateEnumerator = interface; + + + ILEDFunctionProvider = interface + ['{B38F6F90-DC96-42CE-B8F0-21F0DD8AA537}'] + function GetUID: string; + function GetEnumerator: ILEDFunctionEnumerator; + + function Find(const AFunctionUID: string): ILEDFunction; + end; + + + ILEDFunction = interface + ['{7087067A-1016-4A7D-ACB1-BA1F388DAD6C}'] + function GetCategoryName: string; + function GetDisplayName: string; + function GetUID: string; + + function CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): ILEDFunctionWorker; + end; + + + ILEDMultiStateFunction = interface(ILEDFunction) + ['{F16ADF7E-1C1C-4676-8D4F-135B68A80B52}'] + function GetEnumerator: ILEDStateEnumerator; + end; + + + ILEDFunctionObserver = interface + ['{B78415C9-9F64-4AF1-8983-BACE2B7225EF}'] + procedure ObserveUpdate(Sender: ILEDFunctionWorker); + end; + + + ILEDFunctionWorker = interface + ['{5EF3230D-B52F-4BD6-8AD3-F3A035F155B1}'] + procedure Attach(AObserver: ILEDFunctionObserver); + procedure Detach(AObserver: ILEDFunctionObserver); + + function GetProviderUID: string; + function GetFunctionUID: string; + + function GetCurrentState: ILEDStateWorker; + end; + + + ILEDFunctionWorkerSettings = interface + ['{8FA287F6-9FE6-4A49-9C87-05C7F3F2B256}'] + function GetStateColor(const AUID: string; out AColor: TLEDColor): Boolean; + end; + + + ILEDFunctionEnumerator = interface + ['{A03E4E54-19CB-4C08-AD5F-20265817086D}'] + function GetCurrent: ILEDFunction; + function MoveNext: Boolean; + + property Current: ILEDFunction read GetCurrent; + end; + + + ILEDStateEnumerator = interface + ['{045E8466-831A-4704-ABBB-31E85789F314}'] + function GetCurrent: ILEDState; + function MoveNext: Boolean; + + property Current: ILEDState read GetCurrent; + end; + + +implementation + +end. diff --git a/G940LEDControl/Units/LEDFunctionMap.pas b/G940LEDControl/Units/LEDFunctionMap.pas deleted file mode 100644 index 2b68e7a..0000000 --- a/G940LEDControl/Units/LEDFunctionMap.pas +++ /dev/null @@ -1,228 +0,0 @@ -unit LEDFunctionMap; - -interface -uses - Classes, - SyncObjs, - - X2UtHashes; - - -type - TLEDState = (lsOff, lsGreen, lsAmber, lsRed, lsWarning, lsError); - TLEDStateSet = set of TLEDState; - - - TLEDFunctionMap = class(TObject) - private - FFunctions: TX2IIHash; - public - constructor Create; - destructor Destroy; override; - - procedure Clear; - - procedure SetFunction(ALEDIndex, AFunction: Integer); - function GetFunction(ALEDIndex: Integer): Integer; - - function HasFunction(AFunction: Integer): Boolean; overload; - function HasFunction(AFunctions: array of Integer): Boolean; overload; - - function FindFirst(AFunction: Integer; out ALEDIndex: Integer): Boolean; - function FindNext(AFunction: Integer; out ALEDIndex: Integer): Boolean; - end; - - - TLEDStateMap = class(TObject) - private - FStates: TX2IIHash; - public - constructor Create; - destructor Destroy; override; - - procedure Clear; - - function GetState(ALEDIndex: Integer; ADefault: TLEDState = lsGreen): TLEDState; - function SetState(ALEDIndex: Integer; AState: TLEDState): Boolean; - - function HasStates(AStates: TLEDStateSet): Boolean; - function FindFirst(AStates: TLEDStateSet; out ALEDIndex: Integer; out AState: TLEDState): Boolean; - function FindNext(AStates: TLEDStateSet; out ALEDIndex: Integer; out AState: TLEDState): Boolean; - end; - - -const - FUNCTION_NONE = 0; - FUNCTION_OFF = 1; - FUNCTION_RED = 2; - FUNCTION_AMBER = 3; - FUNCTION_GREEN = 4; - - { Note: if this offset ever changes, make sure to write a conversion for - existing configurations. And probably reserve a bit more. } - FUNCTION_PROVIDER_OFFSET = FUNCTION_GREEN; - - -implementation -uses - SysUtils; - - -{ TLEDFunctionMap } -constructor TLEDFunctionMap.Create; -begin - inherited; - - FFunctions := TX2IIHash.Create; -end; - - -destructor TLEDFunctionMap.Destroy; -begin - FreeAndNil(FFunctions); - - inherited; -end; - - -procedure TLEDFunctionMap.Clear; -begin - FFunctions.Clear; -end; - - -procedure TLEDFunctionMap.SetFunction(ALEDIndex, AFunction: Integer); -begin - FFunctions[ALEDIndex] := AFunction; -end; - - -function TLEDFunctionMap.GetFunction(ALEDIndex: Integer): Integer; -begin - Result := FFunctions[ALEDIndex]; -end; - - -function TLEDFunctionMap.HasFunction(AFunctions: array of Integer): Boolean; -var - functionNo: Integer; - -begin - Result := False; - - for functionNo in AFunctions do - begin - Result := HasFunction(functionNo); - if Result then - break; - end; -end; - -function TLEDFunctionMap.HasFunction(AFunction: Integer): Boolean; -var - ledIndex: Integer; - -begin - Result := FindFirst(AFunction, ledIndex); -end; - - -function TLEDFunctionMap.FindFirst(AFunction: Integer; out ALEDIndex: Integer): Boolean; -begin - FFunctions.First; - Result := FindNext(AFunction, ALEDIndex); -end; - - -function TLEDFunctionMap.FindNext(AFunction: Integer; out ALEDIndex: Integer): Boolean; -begin - Result := False; - - while FFunctions.Next do - begin - if FFunctions.CurrentValue = AFunction then - begin - ALEDIndex := FFunctions.CurrentKey; - Result := True; - break; - end; - end; -end; - - -{ TLEDStateMap } -constructor TLEDStateMap.Create; -begin - inherited; - - FStates := TX2IIHash.Create; -end; - - -destructor TLEDStateMap.Destroy; -begin - FreeAndNil(FStates); - - inherited; -end; - - -procedure TLEDStateMap.Clear; -begin - FStates.Clear; -end; - - -function TLEDStateMap.GetState(ALEDIndex: Integer; ADefault: TLEDState): TLEDState; -begin - Result := ADefault; - if FStates.Exists(ALEDIndex) then - Result := TLEDState(FStates[ALEDIndex]); -end; - - -function TLEDStateMap.SetState(ALEDIndex: Integer; AState: TLEDState): Boolean; -begin - if FStates.Exists(ALEDIndex) then - Result := (FStates[ALEDIndex] <> Ord(AState)) - else - Result := True; - - if Result then - FStates[ALEDIndex] := Ord(AState); -end; - - -function TLEDStateMap.HasStates(AStates: TLEDStateSet): Boolean; -var - ledIndex: Integer; - state: TLEDState; - -begin - Result := FindFirst(AStates, ledIndex, state); -end; - - - -function TLEDStateMap.FindFirst(AStates: TLEDStateSet; out ALEDIndex: Integer; out AState: TLEDState): Boolean; -begin - FStates.First; - Result := FindNext(AStates, ALEDIndex, AState); -end; - - -function TLEDStateMap.FindNext(AStates: TLEDStateSet; out ALEDIndex: Integer; out AState: TLEDState): Boolean; -begin - Result := False; - - while FStates.Next do - if TLEDState(FStates.CurrentValue) in AStates then - begin - ALEDIndex := FStates.CurrentKey; - AState := TLEDState(FStates.CurrentValue); - Result := True; - break; - end; -end; - -end. diff --git a/G940LEDControl/Units/LEDFunctionRegistry.pas b/G940LEDControl/Units/LEDFunctionRegistry.pas new file mode 100644 index 0000000..a4605f7 --- /dev/null +++ b/G940LEDControl/Units/LEDFunctionRegistry.pas @@ -0,0 +1,216 @@ +unit LEDFunctionRegistry; + +interface +uses + Classes, + + LEDFunctionIntf; + + +type + TLEDFunctionProviderList = class; + + TLEDFunctionRegistry = class(TObject) + private + FProviders: TLEDFunctionProviderList; + protected + class function Instance: TLEDFunctionRegistry; + + procedure DoRegister(AProvider: ILEDFunctionProvider); + procedure DoUnregister(AProvider: ILEDFunctionProvider); + function DoFind(const AUID: string): ILEDFunctionProvider; + + function GetProviders: TLEDFunctionProviderList; + public + constructor Create; + destructor Destroy; override; + + class procedure Register(AProvider: ILEDFunctionProvider); + class procedure Unregister(AProvider: ILEDFunctionProvider); + + class function Find(const AUID: string): ILEDFunctionProvider; + + class function Providers: TLEDFunctionProviderList; + end; + + + TLEDFunctionProviderListEnumerator = class; + + TLEDFunctionProviderList = class(TObject) + private + FList: TInterfaceList; + protected + procedure Add(AProvider: ILEDFunctionProvider); + procedure Remove(AProvider: ILEDFunctionProvider); + public + constructor Create; + destructor Destroy; override; + + function Find(const AUID: string): ILEDFunctionProvider; + + function GetEnumerator: TLEDFunctionProviderListEnumerator; + end; + + + TLEDFunctionProviderListEnumerator = class(TInterfaceListEnumerator) + public + function GetCurrent: ILEDFunctionProvider; inline; + property Current: ILEDFunctionProvider read GetCurrent; + end; + + +implementation +uses + SysUtils; + + +var + RegistryInstance: TLEDFunctionRegistry; + + +{ TLEDFunctionRegistry } +class procedure TLEDFunctionRegistry.Register(AProvider: ILEDFunctionProvider); +begin + Instance.DoRegister(AProvider); +end; + + +class procedure TLEDFunctionRegistry.Unregister(AProvider: ILEDFunctionProvider); +begin + Instance.DoUnregister(AProvider); +end; + + +class function TLEDFunctionRegistry.Find(const AUID: string): ILEDFunctionProvider; +begin + Result := Instance.DoFind(AUID); +end; + + +class function TLEDFunctionRegistry.Providers: TLEDFunctionProviderList; +begin + Result := Instance.GetProviders; +end; + + +class function TLEDFunctionRegistry.Instance: TLEDFunctionRegistry; +begin + if not Assigned(RegistryInstance) then + RegistryInstance := TLEDFunctionRegistry.Create; + + Result := RegistryInstance; +end; + + +constructor TLEDFunctionRegistry.Create; +begin + inherited Create; + + FProviders := TLEDFunctionProviderList.Create; +end; + + +destructor TLEDFunctionRegistry.Destroy; +begin + FreeAndNil(FProviders); + + inherited Destroy; +end; + + +procedure TLEDFunctionRegistry.DoRegister(AProvider: ILEDFunctionProvider); +begin + FProviders.Add(AProvider); +end; + + +procedure TLEDFunctionRegistry.DoUnregister(AProvider: ILEDFunctionProvider); +begin + FProviders.Remove(AProvider); +end; + + +function TLEDFunctionRegistry.DoFind(const AUID: string): ILEDFunctionProvider; +begin + Result := FProviders.Find(AUID); +end; + + +function TLEDFunctionRegistry.GetProviders: TLEDFunctionProviderList; +begin + Result := FProviders; +end; + + +{ TLEDFunctionProviderList } +constructor TLEDFunctionProviderList.Create; +begin + inherited Create; + + FList := TInterfaceList.Create; +end; + + +destructor TLEDFunctionProviderList.Destroy; +begin + FreeAndNil(FList); + + inherited Destroy; +end; + + +function TLEDFunctionProviderList.Find(const AUID: string): ILEDFunctionProvider; +var + provider: ILEDFunctionProvider; + +begin + Result := nil; + + for provider in Self do + if provider.GetUID = AUID then + begin + Result := provider; + break; + end; +end; + +procedure TLEDFunctionProviderList.Add(AProvider: ILEDFunctionProvider); +var + stableReference: ILEDFunctionProvider; + +begin + stableReference := (AProvider as ILEDFunctionProvider); + if FList.IndexOf(stableReference) = -1 then + FList.Add(stableReference); +end; + + +procedure TLEDFunctionProviderList.Remove(AProvider: ILEDFunctionProvider); +var + index: Integer; + +begin + index := FList.IndexOf(AProvider as ILEDFunctionProvider); + if index > -1 then + FList.Delete(index); +end; + + +function TLEDFunctionProviderList.GetEnumerator: TLEDFunctionProviderListEnumerator; +begin + Result := TLEDFunctionProviderListEnumerator.Create(FList); +end; + + +{ TLEDFunctionProviderListEnumerator } +function TLEDFunctionProviderListEnumerator.GetCurrent: ILEDFunctionProvider; +begin + Result := ((inherited GetCurrent) as ILEDFunctionProvider); +end; + + +initialization +finalization + FreeAndNil(RegistryInstance); + +end. diff --git a/G940LEDControl/Units/LEDResources.pas b/G940LEDControl/Units/LEDResources.pas new file mode 100644 index 0000000..c7e98d7 --- /dev/null +++ b/G940LEDControl/Units/LEDResources.pas @@ -0,0 +1,61 @@ +unit LEDResources; + +interface +uses + LEDColorIntf; + + +const + LEDColorUID: array[TLEDColor] of string = + ( + 'off', + 'green', + 'amber', + 'red', + 'green.flashing.fast', + 'green.flashing', + 'amber.flashing.fast', + 'amber.flashing', + 'red.flashing.fast', + 'red.flashing' + ); + + LEDColorDisplayName: array[TLEDColor] of string = + ( + 'Off', + 'Green', + 'Amber', + 'Red', + 'Flashing green (fast)', + 'Flashing green (normal)', + 'Flashing amber (fast)', + 'Flashing amber (normal)', + 'Flashing red (fast)', + 'Flashing red (normal)' + ); + + + function StringToLEDColor(const AValue: string; out AColor: TLEDColor): Boolean; + +implementation + + +function StringToLEDColor(const AValue: string; out AColor: TLEDColor): Boolean; +var + color: TLEDColor; + +begin + Result := False; + + for color := Low(TLEDColor) to High(TLEDColor) do + begin + if LEDColorUID[color] = AValue then + begin + Result := True; + AColor := color; + break; + end; + end; +end; + +end. diff --git a/G940LEDControl/Units/LEDState.pas b/G940LEDControl/Units/LEDState.pas new file mode 100644 index 0000000..cd3aace --- /dev/null +++ b/G940LEDControl/Units/LEDState.pas @@ -0,0 +1,99 @@ +unit LEDState; + +interface +uses + LEDColorIntf, + LEDStateIntf; + + +type + TCustomLEDState = class(TInterfacedObject, ICustomLEDState) + private + FUID: string; + protected + { ICustomLEDState } + function GetUID: string; + public + constructor Create(const AUID: string); + end; + + + TLEDState = class(TCustomLEDState, ILEDState) + private + FDisplayName: string; + FDefaultColor: TLEDColor; + protected + { ILEDState } + function GetDisplayName: string; + function GetDefaultColor: TLEDColor; + public + constructor Create(const AUID, ADisplayName: string; ADefaultColor: TLEDColor); + end; + + + TLEDStateWorker = class(TCustomLEDState, ILEDStateWorker) + private + FColor: ILEDStateColor; + protected + { ILEDStateWorker } + function GetColor: ILEDStateColor; + public + constructor Create(const AUID: string; AColor: ILEDStateColor); + end; + + +implementation + + +{ TCustomLEDState } +constructor TCustomLEDState.Create(const AUID: string); +begin + inherited Create; + + FUID := AUID; +end; + + +function TCustomLEDState.GetUID: string; +begin + Result := FUID; +end; + + +{ TLEDState } +constructor TLEDState.Create(const AUID, ADisplayName: string; ADefaultColor: TLEDColor); +begin + inherited Create(AUID); + + FDisplayName := ADisplayName; + FDefaultColor := ADefaultColor; +end; + + +function TLEDState.GetDisplayName: string; +begin + Result := FDisplayName; +end; + + +function TLEDState.GetDefaultColor: TLEDColor; +begin + Result := FDefaultColor; +end; + + +{ TLEDStateWorker } +constructor TLEDStateWorker.Create(const AUID: string; AColor: ILEDStateColor); +begin + inherited Create(AUID); + + FColor := AColor; +end; + + +function TLEDStateWorker.GetColor: ILEDStateColor; +begin + Result := FColor; +end; + +end. diff --git a/G940LEDControl/Units/LEDStateConsumer.pas b/G940LEDControl/Units/LEDStateConsumer.pas index e0aac12..d20f05b 100644 --- a/G940LEDControl/Units/LEDStateConsumer.pas +++ b/G940LEDControl/Units/LEDStateConsumer.pas @@ -2,351 +2,308 @@ unit LEDStateConsumer; interface uses + System.Classes, + OtlComm, OtlCommon, OtlTaskControl, - LEDFunctionMap, - LEDStateProvider; + LEDColorIntf, + LEDFunctionIntf, + Profile; const - MSG_CLEAR_FUNCTIONS = 1001; - MSG_SET_FUNCTION = 1002; - MSG_INITIALIZE_PROVIDER = 1003; - MSG_FINALIZE_PROVIDER = 1004; - MSG_PROCESS_MESSAGES = 1005; - MSG_FINALIZE = 1006; + TM_LOADPROFILE = 1001; + TM_TICK = 1002; - MSG_PROVIDER_KILLED = 1007; - MSG_RUN_IN_MAINTHREAD = 1008; - - MSG_CONSUMER_OFFSET = MSG_RUN_IN_MAINTHREAD; - - TIMER_PROCESSMESSAGES = 1001; - TIMER_CONSUMER_OFFSET = TIMER_PROCESSMESSAGES; + TIMER_TICK = 101; type - { This interface name made me giggle. Because it's true. } - IRunInMainThread = interface(IOmniWaitableValue) - ['{68B8F2F7-ED40-4078-9D99-503D7AFA068B}'] - procedure Execute; - end; - - - TLEDStateConsumer = class(TOmniWorker, ILEDStateConsumer) + TLEDStateConsumer = class(TOmniWorker, ILEDFunctionObserver) private - FFunctionMap: TLEDFunctionMap; - FStateMap: TLEDStateMap; - FProvider: TLEDStateProvider; - FTimerSet: Boolean; - FChanged: Boolean; - FUpdateCount: Integer; - FDestroying: Boolean; + FButtonWorkers: TInterfaceList; + FButtonColors: TInterfaceList; + FHasTickTimer: Boolean; protected - procedure MsgClearFunctions(var msg: TOmniMessage); message MSG_CLEAR_FUNCTIONS; - procedure MsgSetFunction(var msg: TOmniMessage); message MSG_SET_FUNCTION; - procedure MsgInitializeProvider(var msg: TOmniMessage); message MSG_INITIALIZE_PROVIDER; - procedure MsgFinalizeProvider(var msg: TOmniMessage); message MSG_FINALIZE_PROVIDER; - procedure MsgProcessMessages(var msg: TOmniMessage); message MSG_PROCESS_MESSAGES; - procedure MsgFinalize(var msg: TOmniMessage); message MSG_FINALIZE; - + function Initialize: Boolean; override; procedure Cleanup; override; - procedure InitializeProvider(AProviderClass: TLEDStateProviderClass); - procedure FinalizeProvider; + function CreateWorker(AProfileButton: TProfileButton; const APreviousState: string): ILEDFunctionWorker; + + property ButtonWorkers: TInterfaceList read FButtonWorkers; + property ButtonColors: TInterfaceList read FButtonColors; + property HasTickTimer: Boolean read FHasTickTimer; + protected + { ILEDFunctionObserver } + procedure ObserveUpdate(Sender: ILEDFunctionWorker); - procedure RunInMainThread(AExecutor: IRunInMainThread; AWait: Boolean = False); - procedure InitializeLEDState; virtual; - procedure ResetLEDState; virtual; - procedure LEDStateChanged(ALEDIndex: Integer; AState: TLEDState); virtual; procedure Changed; virtual; - - { ILEDStateConsumer } - function GetFunctionMap: TLEDFunctionMap; - procedure SetStateByFunction(AFunction: Integer; AState: TLEDState); - - property Destroying: Boolean read FDestroying; - property FunctionMap: TLEDFunctionMap read GetFunctionMap; - property StateMap: TLEDStateMap read FStateMap; - property Provider: TLEDStateProvider read FProvider; - property UpdateCount: Integer read FUpdateCount write FUpdateCount; - public - constructor Create; - - procedure BeginUpdate; - procedure EndUpdate; + procedure Update; virtual; abstract; + protected + procedure TMLoadProfile(var Msg: TOmniMessage); message TM_LOADPROFILE; + procedure TMTick(var Msg: TOmniMessage); message TM_TICK; end; - procedure ClearFunctions(AConsumer: IOmniTaskControl); - procedure SetFunction(AConsumer: IOmniTaskControl; ALEDIndex, AFunction: Integer); - procedure InitializeStateProvider(AConsumer: IOmniTaskControl; AProviderClass: TLEDStateProviderClass); - procedure FinalizeStateProvider(AConsumer: IOmniTaskControl); - procedure Finalize(AConsumer: IOmniTaskControl); - - implementation uses - SysUtils, - Windows; + Generics.Collections, + System.SysUtils, + Winapi.Windows, + + LEDFunctionRegistry, + LEDStateIntf; const - G940_LED_COUNT = 8; + INTERVAL_TICK = 500; + + +type + TProfileButtonWorkerSettings = class(TInterfacedObject, ILEDFunctionWorkerSettings) + private + FProfileButton: TProfileButton; + protected + { ILEDFunctionWorkerSettings } + function GetStateColor(const AUID: string; out AColor: TLEDColor): Boolean; + + property ProfileButton: TProfileButton read FProfileButton; + public + constructor Create(AProfileButton: TProfileButton); + end; { TLEDStateConsumer } -constructor TLEDStateConsumer.Create; +function TLEDStateConsumer.Initialize: Boolean; begin - inherited; + Result := inherited Initialize; + if not Result then + exit; - FFunctionMap := TLEDFunctionMap.Create; - FStateMap := TLEDStateMap.Create; - - InitializeLEDState; + FButtonWorkers := TInterfaceList.Create; + FButtonColors := TInterfaceList.Create; end; procedure TLEDStateConsumer.Cleanup; begin - inherited; + FreeAndNil(FButtonColors); + FreeAndNil(FButtonWorkers); - FreeAndNil(FStateMap); - FreeAndNil(FFunctionMap); + inherited Cleanup; end; -procedure TLEDStateConsumer.BeginUpdate; -begin - if FUpdateCount = 0 then - FChanged := False; - - Inc(FUpdateCount); -end; - - -procedure TLEDStateConsumer.EndUpdate; -begin - if FUpdateCount > 0 then - Dec(FUpdateCount); - - if (FUpdateCount = 0) and FChanged then - Changed; -end; - - -function TLEDStateConsumer.GetFunctionMap: TLEDFunctionMap; -begin - Result := FFunctionMap; -end; - - -procedure TLEDStateConsumer.SetStateByFunction(AFunction: Integer; AState: TLEDState); +function TLEDStateConsumer.CreateWorker(AProfileButton: TProfileButton; const APreviousState: string): ILEDFunctionWorker; var - ledIndex: Integer; + provider: ILEDFunctionProvider; + ledFunction: ILEDFunction; begin - if FunctionMap.FindFirst(AFunction, ledIndex) then - repeat - if StateMap.SetState(ledIndex, AState) then - LEDStateChanged(ledIndex, AState); - until not FunctionMap.FindNext(AFunction, ledIndex); -end; + Result := nil; - - -procedure TLEDStateConsumer.MsgClearFunctions(var msg: TOmniMessage); -begin - FunctionMap.Clear; -end; - - -procedure TLEDStateConsumer.MsgSetFunction(var msg: TOmniMessage); -var - values: TOmniValueContainer; - -begin - values := msg.MsgData.AsArray; - FunctionMap.SetFunction(values[0], values[1]); -end; - - -procedure TLEDStateConsumer.MsgInitializeProvider(var msg: TOmniMessage); -begin - InitializeProvider(TLEDStateProviderClass(msg.MsgData.AsPointer)); -end; - - -procedure TLEDStateConsumer.MsgFinalizeProvider(var msg: TOmniMessage); -begin - FinalizeProvider; -end; - - -procedure TLEDStateConsumer.MsgProcessMessages(var msg: TOmniMessage); -begin - BeginUpdate; - try - Provider.ProcessMessages; - - if Provider.Terminated then - begin - FinalizeProvider; - Task.Comm.Send(MSG_PROVIDER_KILLED, ''); - end; - finally - EndUpdate; - end; -end; - - -procedure TLEDStateConsumer.MsgFinalize(var msg: TOmniMessage); -begin - FDestroying := True; - FinalizeProvider; - Task.Terminate; -end; - - -procedure TLEDStateConsumer.InitializeProvider(AProviderClass: TLEDStateProviderClass); -begin - FinalizeProvider; - - FProvider := AProviderClass.Create(Self); - try - Provider.Initialize; - - if Provider.ProcessMessagesInterval > -1 then - begin - Task.SetTimer(TIMER_PROCESSMESSAGES, Provider.ProcessMessagesInterval, MSG_PROCESS_MESSAGES); - FTimerSet := True; - end; - - InitializeLEDState; - except - on E:Exception do - begin - FProvider := nil; - Task.Comm.Send(MSG_PROVIDER_KILLED, E.Message); - end; - end; -end; - - -procedure TLEDStateConsumer.FinalizeProvider; -begin - if Assigned(Provider) then + provider := TLEDFunctionRegistry.Find(AProfileButton.ProviderUID); + if Assigned(provider) then begin - if FTimerSet then - begin - Task.ClearTimer(TIMER_PROCESSMESSAGES); - FTimerSet := False; - end; - - Provider.Terminate; - Provider.Finalize; - FreeAndNil(FProvider); - - StateMap.Clear; - ResetLEDState; + ledFunction := provider.Find(AProfileButton.FunctionUID); + if Assigned(ledFunction) then + Result := ledFunction.CreateWorker(TProfileButtonWorkerSettings.Create(AProfileButton), APreviousState); end; end; -procedure TLEDStateConsumer.RunInMainThread(AExecutor: IRunInMainThread; AWait: Boolean); -begin - Task.Comm.Send(MSG_RUN_IN_MAINTHREAD, AExecutor); - if AWait then - AExecutor.WaitFor(INFINITE); -end; - - -procedure TLEDStateConsumer.InitializeLEDState; -var - ledIndex: Integer; - state: TLEDState; - newState: TLEDState; - -begin - BeginUpdate; - try - ResetLEDState; - - for ledIndex := 0 to Pred(G940_LED_COUNT) do - begin - state := StateMap.GetState(ledIndex, lsGreen); - newState := state; - - case FunctionMap.GetFunction(ledIndex) of - FUNCTION_OFF: newState := lsOff; - FUNCTION_RED: newState := lsRed; - FUNCTION_AMBER: newState := lsAmber; - FUNCTION_GREEN: newState := lsGreen; - end; - - if state <> newState then - LEDStateChanged(ledIndex, newState); - end; - finally - EndUpdate; - end; -end; - - -procedure TLEDStateConsumer.ResetLEDState; -begin - if UpdateCount = 0 then - Changed - else - FChanged := True; -end; - - -procedure TLEDStateConsumer.LEDStateChanged(ALEDIndex: Integer; AState: TLEDState); -begin - if UpdateCount = 0 then - Changed - else - FChanged := True; -end; - - procedure TLEDStateConsumer.Changed; +var + hasDynamicColors: Boolean; + buttonIndex: Integer; + state: ILEDStateWorker; + color: ILEDStateColor; + dynamicColor: ILEDStateDynamicColor; + begin - FChanged := False; + hasDynamicColors := False; + ButtonColors.Clear; + + for buttonIndex := 0 to Pred(ButtonWorkers.Count) do + begin + color := nil; + + if Assigned(ButtonWorkers[buttonIndex]) then + begin + state := (ButtonWorkers[buttonIndex] as ILEDFunctionWorker).GetCurrentState; + if Assigned(state) then + begin + color := state.GetColor; + if Assigned(color) then + begin + if (hasDynamicColors = False) and Supports(color, ILEDStateDynamicColor, dynamicColor) then + begin + { If the tick timer isn't currently running, there were no + dynamic colors before. Reset each dynamic colors now. } + if not HasTickTimer then + dynamicColor.Reset; + + hasDynamicColors := True; + end; + + ButtonColors.Add(color as ILEDStateColor); + end; + end; + end; + + if not Assigned(color) then + ButtonColors.Add(nil); + end; + + if hasDynamicColors <> HasTickTimer then + begin + if hasDynamicColors then + Task.SetTimer(TIMER_TICK, INTERVAL_TICK, TM_TICK) + else + Task.ClearTimer(TIMER_TICK); + end; + + Update; end; -{ Helpers } -procedure ClearFunctions(AConsumer: IOmniTaskControl); +procedure TLEDStateConsumer.ObserveUpdate(Sender: ILEDFunctionWorker); begin - AConsumer.Comm.Send(MSG_CLEAR_FUNCTIONS); + Changed; end; -procedure SetFunction(AConsumer: IOmniTaskControl; ALEDIndex, AFunction: Integer); +procedure TLEDStateConsumer.TMLoadProfile(var Msg: TOmniMessage); + + function GetFunctionKey(const AProviderUID, AFunctionUID: string): string; inline; + begin + Result := AProviderUID + '|' + AFunctionUID; + end; + + +var + oldWorkers: TInterfaceList; + oldStates: TDictionary; + oldWorker: IInterface; + profile: TProfile; + buttonIndex: Integer; + worker: ILEDFunctionWorker; + state: ILEDStateWorker; + previousState: string; + button: TProfileButton; + functionKey: string; + begin - AConsumer.Comm.Send(MSG_SET_FUNCTION, [ALEDIndex, AFunction]); + profile := Msg.MsgData; + + oldStates := nil; + oldWorkers := nil; + try + oldStates := TDictionary.Create; + oldWorkers := TInterfaceList.Create; + + { Keep a copy of the old workers until all the new ones are initialized, + so we don't get unneccessary SimConnect reconnects. } + for oldWorker in ButtonWorkers do + begin + if Assigned(oldWorker) then + begin + worker := (oldWorker as ILEDFunctionWorker); + try + worker.Detach(Self); + oldWorkers.Add(worker); + + { Keep the current state as well, to prevent the LEDs from flickering } + state := worker.GetCurrentState; + try + oldStates.AddOrSetValue(GetFunctionKey(worker.GetProviderUID, worker.GetFunctionUID), state.GetUID); + finally + state := nil; + end; + finally + worker := nil; + end; + end; + end; + + ButtonWorkers.Clear; + + for buttonIndex := 0 to Pred(profile.ButtonCount) do + begin + if profile.HasButton(buttonIndex) then + begin + button := profile.Buttons[buttonIndex]; + + previousState := ''; + functionKey := GetFunctionKey(button.ProviderUID, button.FunctionUID); + if oldStates.ContainsKey(functionKey) then + previousState := oldStates[functionKey]; + + worker := CreateWorker(button, previousState) as ILEDFunctionWorker; + ButtonWorkers.Add(worker); + + if Assigned(worker) then + worker.Attach(Self); + end else + ButtonWorkers.Add(nil); + end; + finally + FreeAndNil(oldWorkers); + FreeAndNil(oldStates); + end; + + Changed; end; -procedure InitializeStateProvider(AConsumer: IOmniTaskControl; AProviderClass: TLEDStateProviderClass); +procedure TLEDStateConsumer.TMTick(var Msg: TOmniMessage); +var + buttonIndex: Integer; + checkButtonIndex: Integer; + alreadyTicked: Boolean; + color: ILEDStateColor; + dynamicColor: ILEDStateDynamicColor; + begin - AConsumer.Comm.Send(MSG_INITIALIZE_PROVIDER, Pointer(AProviderClass)); + // (MvR) 19-2-2013: I could pass a tick count to Tick() so that they can all use modulus to blink synchronously... think about it. + + for buttonIndex := 0 to Pred(ButtonColors.Count) do + begin + alreadyTicked := False; + color := (ButtonColors[buttonIndex] as ILEDStateColor); + + if Supports(color, ILEDStateDynamicColor, dynamicColor) then + begin + { Check if this color has already been ticked } + for checkButtonIndex := Pred(buttonIndex) downto 0 do + if (ButtonColors[checkButtonIndex] as ILEDStateColor) = color then + begin + alreadyTicked := True; + break; + end; + + if not alreadyTicked then + dynamicColor.Tick; + end; + end; + + Update; end; -procedure FinalizeStateProvider(AConsumer: IOmniTaskControl); +{ TProfileButtonWorkerSettings } +constructor TProfileButtonWorkerSettings.Create(AProfileButton: TProfileButton); begin - AConsumer.Comm.Send(MSG_FINALIZE_PROVIDER); + inherited Create; + + FProfileButton := AProfileButton; end; - -procedure Finalize(AConsumer: IOmniTaskControl); +function TProfileButtonWorkerSettings.GetStateColor(const AUID: string; out AColor: TLEDColor): Boolean; begin - AConsumer.Comm.Send(MSG_FINALIZE); + Result := ProfileButton.GetStateColor(AUID, AColor); end; end. diff --git a/G940LEDControl/Units/LEDStateIntf.pas b/G940LEDControl/Units/LEDStateIntf.pas new file mode 100644 index 0000000..ad40fa1 --- /dev/null +++ b/G940LEDControl/Units/LEDStateIntf.pas @@ -0,0 +1,30 @@ +unit LEDStateIntf; + +interface +uses + LEDColorIntf; + + +type + ICustomLEDState = interface + ['{B5567129-74E1-4888-83F5-8A6174706671}'] + function GetUID: string; + end; + + + ILEDState = interface(ICustomLEDState) + ['{2C91D49C-2B67-42A3-B5EF-475976DD33F8}'] + function GetDisplayName: string; + function GetDefaultColor: TLEDColor; + end; + + + ILEDStateWorker = interface(ICustomLEDState) + ['{0361CBD5-E64E-4972-A8A4-D5FE0B0DFB1C}'] + function GetColor: ILEDStateColor; + end; + + +implementation + +end. diff --git a/G940LEDControl/Units/LEDStateProvider.pas b/G940LEDControl/Units/LEDStateProvider.pas deleted file mode 100644 index ba81459..0000000 --- a/G940LEDControl/Units/LEDStateProvider.pas +++ /dev/null @@ -1,127 +0,0 @@ -unit LEDStateProvider; - -interface -uses - Classes, - SyncObjs, - SysUtils, - - LEDFunctionMap; - -type - EInitializeError = class(Exception); - - ILEDStateConsumer = interface - ['{6E630C92-7C5C-4D16-8BED-AE27559FA584}'] - function GetFunctionMap: TLEDFunctionMap; - procedure SetStateByFunction(AFunction: Integer; AState: TLEDState); - - property FunctionMap: TLEDFunctionMap read GetFunctionMap; - end; - - - IFunctionConsumer = interface - ['{97B47A29-BA7F-4C48-934D-EB66D2741647}'] - procedure SetCategory(const ACategory: string); - procedure AddFunction(AFunction: Integer; const ADescription: string); - end; - - - TLEDStateProvider = class(TObject) - private - FConsumer: ILEDStateConsumer; - FTerminated: Boolean; - protected - procedure Execute; virtual; abstract; - - function GetProcessMessagesInterval: Integer; virtual; - - property Consumer: ILEDStateConsumer read FConsumer; - public - class procedure EnumFunctions(AConsumer: IFunctionConsumer); virtual; - - constructor Create(AConsumer: ILEDStateConsumer); virtual; - destructor Destroy; override; - - procedure Initialize; virtual; - procedure Finalize; virtual; - procedure ProcessMessages; virtual; - - procedure Terminate; virtual; - - property ProcessMessagesInterval: Integer read GetProcessMessagesInterval; - property Terminated: Boolean read FTerminated; - end; - - TLEDStateProviderClass = class of TLEDStateProvider; - - -const - EXIT_SUCCESS = 0; - EXIT_ERROR = 1; - - EXIT_CONSUMER_OFFSET = 100; - EXIT_PROVIDER_OFFSET = 200; - -implementation -const - CATEGORY_STATIC = 'Static'; - - FUNCTION_DESC_OFF = 'Light off'; - FUNCTION_DESC_GREEN = 'Green'; - FUNCTION_DESC_AMBER = 'Amber'; - FUNCTION_DESC_RED = 'Red'; - - -{ TCustomLEDStateProvider } -class procedure TLEDStateProvider.EnumFunctions(AConsumer: IFunctionConsumer); -begin - AConsumer.SetCategory(CATEGORY_STATIC); - AConsumer.AddFunction(FUNCTION_OFF, FUNCTION_DESC_OFF); - AConsumer.AddFunction(FUNCTION_GREEN, FUNCTION_DESC_GREEN); - AConsumer.AddFunction(FUNCTION_AMBER, FUNCTION_DESC_AMBER); - AConsumer.AddFunction(FUNCTION_RED, FUNCTION_DESC_RED); -end; - - -constructor TLEDStateProvider.Create(AConsumer: ILEDStateConsumer); -begin - inherited Create; - - FConsumer := AConsumer; -end; - - -destructor TLEDStateProvider.Destroy; -begin - inherited; -end; - - -procedure TLEDStateProvider.Initialize; -begin -end; - - -procedure TLEDStateProvider.Finalize; -begin -end; - - -procedure TLEDStateProvider.ProcessMessages; -begin -end; - - -procedure TLEDStateProvider.Terminate; -begin - FTerminated := True; -end; - - -function TLEDStateProvider.GetProcessMessagesInterval: Integer; -begin - Result := -1; -end; - -end. diff --git a/G940LEDControl/Units/ObserverIntf.pas b/G940LEDControl/Units/ObserverIntf.pas new file mode 100644 index 0000000..c5b0123 --- /dev/null +++ b/G940LEDControl/Units/ObserverIntf.pas @@ -0,0 +1,20 @@ +unit ObserverIntf; + +interface +type + IObserver = interface + ['{B78415C9-9F64-4AF1-8983-BACE2B7225EF}'] + procedure Update(Sender: IInterface); + end; + + + IObservable = interface + ['{BC004BDA-14E4-4923-BE6D-98A0746852F1}'] + procedure Attach(AObserver: IObserver); + procedure Detach(AObserver: IObserver); + end; + + +implementation + +end. diff --git a/G940LEDControl/Units/Profile.pas b/G940LEDControl/Units/Profile.pas new file mode 100644 index 0000000..dfd588d --- /dev/null +++ b/G940LEDControl/Units/Profile.pas @@ -0,0 +1,405 @@ +unit Profile; + +interface +uses + Generics.Collections, + System.Classes, + + X2UtPersistIntf, + + LEDColorIntf; + + +type + TProfileButtonStateColors = TDictionary; + + TProfileButton = class(TPersistent) + private + FProviderUID: string; + FFunctionUID: string; + + FStateColors: TProfileButtonStateColors; + protected + function Load(AReader: IX2PersistReader): Boolean; + procedure Save(AWriter: IX2PersistWriter); + + property StateColors: TProfileButtonStateColors read FStateColors; + public + constructor Create; + destructor Destroy; override; + + procedure Assign(Source: TPersistent); override; + + procedure ClearStateColors; + function GetStateColor(const AStateUID: string; out AValue: TLEDColor): Boolean; + procedure SetStateColor(const AStateUID: string; const AValue: TLEDColor); + + property ProviderUID: string read FProviderUID write FProviderUID; + property FunctionUID: string read FFunctionUID write FFunctionUID; + end; + + + TProfileButtonList = class(TObjectList); + + + TProfile = class(TPersistent) + private + FName: string; + FIsTemporary: Boolean; + FButtons: TProfileButtonList; + + function GetButton(Index: Integer): TProfileButton; + function GetButtonCount: Integer; + protected + procedure Load(AReader: IX2PersistReader); + procedure Save(AWriter: IX2PersistWriter); + public + constructor Create; + destructor Destroy; override; + + procedure Assign(Source: TPersistent); override; + + function HasButton(AIndex: Integer): Boolean; + + property Name: string read FName write FName; + property IsTemporary: Boolean read FIsTemporary write FIsTemporary; + + property ButtonCount: Integer read GetButtonCount; + property Buttons[Index: Integer]: TProfileButton read GetButton; + end; + + + TProfileList = class(TObjectList) + public + function Find(const AName: string): TProfile; + + procedure Load(AReader: IX2PersistReader); + procedure Save(AWriter: IX2PersistWriter); + end; + + +implementation +uses + System.SysUtils, + + LEDResources; + + +const + SectionProfiles = 'Profiles'; + SectionButton = 'Button'; + SectionStates = 'States'; + + KeyProviderUID = 'ProviderUID'; + KeyFunctionUID = 'FunctionUID'; + KeyIsTemporary = 'IsTemporary'; + + +{ TProfileButton } +constructor TProfileButton.Create; +begin + inherited Create; + + FStateColors := TProfileButtonStateColors.Create; +end; + + +destructor TProfileButton.Destroy; +begin + FreeAndNil(FStateColors); + + inherited Destroy; +end; + + +procedure TProfileButton.Assign(Source: TPersistent); +var + sourceButton: TProfileButton; + stateUID: string; + +begin + if Source is TProfileButton then + begin + sourceButton := TProfileButton(Source); + + FProviderUID := sourceButton.ProviderUID; + FFunctionUID := sourceButton.FunctionUID; + + FStateColors.Clear; + for stateUID in sourceButton.StateColors.Keys do + SetStateColor(stateUID, sourceButton.StateColors[stateUID]); + end else + inherited Assign(Source); +end; + + +procedure TProfileButton.ClearStateColors; +begin + FStateColors.Clear; +end; + + +function TProfileButton.GetStateColor(const AStateUID: string; out AValue: TLEDColor): Boolean; +begin + Result := StateColors.TryGetValue(AStateUID, AValue); +end; + + +procedure TProfileButton.SetStateColor(const AStateUID: string; const AValue: TLEDColor); +begin + StateColors.AddOrSetValue(AStateUID, AValue); +end; + + +function TProfileButton.Load(AReader: IX2PersistReader): Boolean; +var + stateUIDs: TStringList; + stateUID: string; + colorUID: string; + color: TLEDColor; + +begin + Result := AReader.ReadString(KeyProviderUID, FProviderUID) and + AReader.ReadString(KeyFunctionUID, FFunctionUID); + + if Result and AReader.BeginSection(SectionStates) then + try + StateColors.Clear; + + stateUIDs := TStringList.Create; + try + AReader.GetKeys(stateUIDs); + + for stateUID in stateUIDs do + begin + if AReader.ReadString(stateUID, colorUID) and + StringToLEDColor(colorUID, color) then + begin + StateColors.Add(stateUID, color); + end; + end; + finally + FreeAndNil(stateUIDs); + end; + finally + AReader.EndSection; + end; +end; + + +procedure TProfileButton.Save(AWriter: IX2PersistWriter); +var + stateUID: string; + +begin + AWriter.WriteString(KeyProviderUID, FProviderUID); + AWriter.WriteString(KeyFunctionUID, FFunctionUID); + + if AWriter.BeginSection(SectionStates) then + try + for stateUID in StateColors.Keys do + AWriter.WriteString(stateUID, LEDColorUID[StateColors[stateUID]]); + finally + AWriter.EndSection; + end; +end; + + +{ TProfile } +constructor TProfile.Create; +begin + inherited Create; + + FButtons := TProfileButtonList.Create(True); +end; + + +destructor TProfile.Destroy; +begin + FreeAndNil(FButtons); + + inherited Destroy; +end; + + +procedure TProfile.Assign(Source: TPersistent); +var + sourceProfile: TProfile; + buttonIndex: Integer; + +begin + if Source is TProfile then + begin + sourceProfile := TProfile(Source); + + FName := sourceProfile.Name; + FIsTemporary := sourceProfile.IsTemporary; + + FButtons.Clear; + for buttonIndex := 0 to Pred(sourceProfile.ButtonCount) do + Buttons[buttonIndex].Assign(sourceProfile.Buttons[buttonIndex]); + end else + inherited Assign(Source); +end; + + +procedure TProfile.Load(AReader: IX2PersistReader); +var + buttonIndex: Integer; + button: TProfileButton; + +begin + buttonIndex := 0; + + if not AReader.ReadBoolean(KeyIsTemporary, FIsTemporary) then + FIsTemporary := False; + + while AReader.BeginSection(SectionButton + IntToStr(buttonIndex)) do + try + button := TProfileButton.Create; + if button.Load(AReader) then + begin + FButtons.Add(button); + end else + begin + FButtons.Add(nil); + FreeAndNil(button); + end; + finally + AReader.EndSection; + Inc(buttonIndex); + end; +end; + + +procedure TProfile.Save(AWriter: IX2PersistWriter); +var + buttonIndex: Integer; + +begin + AWriter.WriteBoolean(KeyIsTemporary, IsTemporary); + + for buttonIndex := 0 to Pred(FButtons.Count) do + begin + if AWriter.BeginSection(SectionButton + IntToStr(buttonIndex)) then + try + if Assigned(FButtons[buttonIndex]) then + FButtons[buttonIndex].Save(AWriter); + finally + AWriter.EndSection; + end; + end; +end; + + +function TProfile.HasButton(AIndex: Integer): Boolean; +begin + Result := (FButtons.Count > AIndex) and + Assigned(FButtons[AIndex]); +end; + + +function TProfile.GetButtonCount: Integer; +begin + Result := FButtons.Count; +end; + + +function TProfile.GetButton(Index: Integer): TProfileButton; +var + oldCount: Integer; + buttonIndex: Integer; + +begin + oldCount := FButtons.Count; + if Index >= oldCount then + begin + FButtons.Count := Succ(Index); + + for buttonIndex := oldCount to Pred(FButtons.Count) do + FButtons[buttonIndex] := nil; + end; + + Result := FButtons[Index]; + if not Assigned(Result) then + begin + Result := TProfileButton.Create; + FButtons[Index] := Result; + end; +end; + + +{ TProfileList } +function TProfileList.Find(const AName: string): TProfile; +var + profile: TProfile; + +begin + Result := nil; + + for profile in Self do + if SameText(profile.Name, AName) then + begin + Result := profile; + break; + end; +end; + + +procedure TProfileList.Load(AReader: IX2PersistReader); +var + profiles: TStringList; + profileName: string; + profile: TProfile; + +begin + if AReader.BeginSection(SectionProfiles) then + try + profiles := TStringList.Create; + try + AReader.GetSections(profiles); + + for profileName in profiles do + begin + if AReader.BeginSection(profileName) then + try + profile := TProfile.Create; + profile.Name := profileName; + profile.Load(AReader); + + Add(profile); + finally + AReader.EndSection; + end; + end; + finally + FreeAndNil(profiles); + end; + finally + AReader.EndSection; + end; +end; + + +procedure TProfileList.Save(AWriter: IX2PersistWriter); +var + profile: TProfile; + +begin + if AWriter.BeginSection(SectionProfiles) then + try + for profile in Self do + begin + if AWriter.BeginSection(profile.Name) then + try + profile.Save(AWriter); + finally + AWriter.EndSection; + end; + end; + finally + AWriter.EndSection; + end; +end; + +end. diff --git a/G940LEDControl/Units/Settings.pas b/G940LEDControl/Units/Settings.pas new file mode 100644 index 0000000..c1fd303 --- /dev/null +++ b/G940LEDControl/Units/Settings.pas @@ -0,0 +1,71 @@ +unit Settings; + +interface +uses + X2UtPersistIntf; + +type + TSettings = class(TObject) + private + FCheckUpdates: Boolean; + FHasCheckUpdates: Boolean; + FActiveProfile: string; + + procedure SetCheckUpdates(const Value: Boolean); + public + procedure Load(AReader: IX2PersistReader); + procedure Save(AWriter: IX2PersistWriter); + + property CheckUpdates: Boolean read FCheckUpdates write SetCheckUpdates; + property HasCheckUpdates: Boolean read FHasCheckUpdates; + + property ActiveProfile: string read FActiveProfile write FActiveProfile; + end; + + +implementation +const + SectionSettings = 'Settings'; + + KeyCheckUpdates = 'CheckUpdates'; + KeyActiveProfile = 'ActiveProfile'; + + +{ TSettings } +procedure TSettings.Load(AReader: IX2PersistReader); +var + value: Boolean; + +begin + if AReader.BeginSection(SectionSettings) then + try + if AReader.ReadBoolean(KeyCheckUpdates, value) then + CheckUpdates := value; + + if not AReader.ReadString(KeyActiveProfile, FActiveProfile) then + FActiveProfile := ''; + finally + AReader.EndSection; + end; +end; + + +procedure TSettings.Save(AWriter: IX2PersistWriter); +begin + if AWriter.BeginSection(SectionSettings) then + try + AWriter.WriteBoolean(KeyCheckUpdates, CheckUpdates); + AWriter.WriteString(KeyActiveProfile, ActiveProfile); + finally + AWriter.EndSection; + end; +end; + + +procedure TSettings.SetCheckUpdates(const Value: Boolean); +begin + FCheckUpdates := Value; + FHasCheckUpdates := True; +end; + +end. diff --git a/G940LEDControl/Units/StaticLEDColor.pas b/G940LEDControl/Units/StaticLEDColor.pas new file mode 100644 index 0000000..59f8464 --- /dev/null +++ b/G940LEDControl/Units/StaticLEDColor.pas @@ -0,0 +1,38 @@ +unit StaticLEDColor; + +interface +uses + LEDColor, + LEDColorIntf; + + +type + TLEDStateStaticColor = class(TCustomLEDStateColor) + private + FColor: TStaticLEDColor; + protected + function GetCurrentColor: TStaticLEDColor; override; + public + constructor Create(AColor: TStaticLEDColor); + end; + + +implementation + + +{ TStaticLEDState } +constructor TLEDStateStaticColor.Create(AColor: TStaticLEDColor); +begin + inherited Create; + + FColor := AColor; +end; + + +function TLEDStateStaticColor.GetCurrentColor: TStaticLEDColor; +begin + Result := FColor; +end; + + +end. diff --git a/G940LEDControl/Units/StaticLEDFunction.pas b/G940LEDControl/Units/StaticLEDFunction.pas new file mode 100644 index 0000000..c3cb7df --- /dev/null +++ b/G940LEDControl/Units/StaticLEDFunction.pas @@ -0,0 +1,121 @@ +unit StaticLEDFunction; + +interface +uses + LEDFunction, + LEDFunctionIntf, + LEDColorIntf, + LEDStateIntf; + + +type + TStaticLEDFunctionProvider = class(TCustomLEDFunctionProvider) + protected + procedure RegisterFunctions; override; + + function GetUID: string; override; + end; + + + TStaticLEDFunction = class(TCustomLEDFunction) + private + FColor: TLEDColor; + protected + function GetCategoryName: string; override; + function GetDisplayName: string; override; + function GetUID: string; override; + + function CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''): ILEDFunctionWorker; override; + public + constructor Create(AColor: TLEDColor); + end; + + +implementation +uses + LEDColorPool, + LEDFunctionRegistry, + LEDState, + StaticResources; + + +type + TStaticLEDFunctionWorker = class(TCustomLEDFunctionWorker) + private + FState: ILEDStateWorker; + protected + function GetCurrentState: ILEDStateWorker; override; + public + constructor Create(const AProviderUID, AFunctionUID: string; AColor: TLEDColor); + end; + + +{ TStaticLEDFunctionProvider } +procedure TStaticLEDFunctionProvider.RegisterFunctions; +var + color: TLEDColor; + +begin + for color := Low(TStaticLEDColor) to High(TStaticLEDColor) do + RegisterFunction(TStaticLEDFunction.Create(color)); +end; + + +function TStaticLEDFunctionProvider.GetUID: string; +begin + Result := StaticProviderUID; +end; + + +{ TStaticLEDFunction } +constructor TStaticLEDFunction.Create(AColor: TLEDColor); +begin + inherited Create; + + FColor := AColor; +end; + + +function TStaticLEDFunction.GetCategoryName: string; +begin + Result := StaticCategory; +end; + + +function TStaticLEDFunction.GetDisplayName: string; +begin + Result := StaticFunctionDisplayName[FColor]; +end; + + +function TStaticLEDFunction.GetUID: string; +begin + Result := StaticFunctionUID[FColor]; +end; + + +function TStaticLEDFunction.CreateWorker(ASettings: ILEDFunctionWorkerSettings; const APreviousState: string): ILEDFunctionWorker; +begin + Result := TStaticLEDFunctionWorker.Create(StaticProviderUID, GetUID, FColor); +end; + + +{ TStaticLEDFunctionWorker } +constructor TStaticLEDFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AColor: TLEDColor); +begin + inherited Create(AProviderUID, AFunctionUID); + + FState := TLEDStateWorker.Create('', TLEDColorPool.GetColor(AColor)); +end; + + +function TStaticLEDFunctionWorker.GetCurrentState: ILEDStateWorker; +begin + Result := FState; +end; + + +initialization + TLEDFunctionRegistry.Register(TStaticLEDFunctionProvider.Create); + +end. diff --git a/G940LEDControl/Units/StaticResources.pas b/G940LEDControl/Units/StaticResources.pas new file mode 100644 index 0000000..b6bf052 --- /dev/null +++ b/G940LEDControl/Units/StaticResources.pas @@ -0,0 +1,31 @@ +unit StaticResources; + +interface +uses + LEDColorIntf; + + +const + StaticProviderUID = 'static'; + StaticFunctionUID: array[TStaticLEDColor] of string = + ( + 'off', + 'green', + 'amber', + 'red' + ); + + + StaticCategory = 'Static'; + StaticFunctionDisplayName: array[TStaticLEDColor] of string = + ( + 'Off', + 'Green', + 'Amber', + 'Red' + ); + + +implementation + +end.