////////////////////////////////////////////////////////////////////////////
//
// This is the engine system behind the editor, it contains everything
// I would have liked to split things up, but seeing as everything plays
// with everything, it makes a very serious problem with circular reference
// That would required typecasting (latebinding) in almost all parts of the code
// and that approach will probably be very slow, rendering the editor
// more or less useless.
//
// -Aaangel
//
// Copyright (c) 2006 by The CoolMon Project
////////////////////////////////////////////////////////////////////////////////
unit Engine;

interface

Uses
  Forms,
  xmldom,
  XMLIntf,
  msxmldom,
  XMLDoc,
  SysUtils,
  Variants,
  messages,
  CM2VisPlugTypes,
  CM2GenPlugTypes,
  CM2PlugInTypes,
  CM2CMLXPlugTypes,
  CM2CMLXInterface,
  Windows,
  ExtCtrls,
  Controls,
  Classes,
  Graphics,
  ComCtrls,
  EditorVariableManager;

Type
////////////////////////////////////////////////////////////////////////////////
// Predecleration - Prototypes
////////////////////////////////////////////////////////////////////////////////
 TEdFormNode = Class;
 TEdRawNode = Class;
 TVisualPanelList = class;
 TEdVisualNode = Class;
 TedDisplayNode = Class;
 TEdParameterBlockNode = class;
 TEdContentNode = Class;
 TEdSensorNode = Class;
 TEdEventsNode = Class;
 TEdParameterNode = Class;
 TEdEventHandlerNode = Class;
 TEdActionNode = Class;
 TEdExecuteNode = Class;

////////////////////////////////////////////////////////////////////////////////
//Base class for TFormPanel and TVisualPanel
////////////////////////////////////////////////////////////////////////////////
  TAlphaPanel = class(TCustomControl)
  private
    fAlpha: byte;
    fBorderColor: TColor;
    fBorderWidth: integer;
    fDrawBorder: boolean;
    fSelected: boolean;
    fTransparent: boolean;
    fTransparentColor: TColor;
    procedure SetAlpha(const Value: byte);
    procedure SetBorderColor(const Value: TColor);
    procedure SetBorderWidth(const Value: integer);
    procedure SetDrawBorder(const Value: boolean);
    procedure SetSelected(const Value: boolean);
    procedure SetTransparent(const Value: boolean);
    procedure SetTransparentColor(const Value: TColor);
  protected
    procedure DoDragDrop(Sender, Source: TObject; X, Y: integer);
    procedure DoDragOver(Sender, Source: TObject; X, Y: integer; State: TDragState; var Accept: boolean);
    procedure DoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure DoMouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
    procedure DoMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
  published
    property Alpha: byte read fAlpha write SetAlpha;
    property BorderColor: TColor read fBorderColor write SetBorderColor;
    property BorderWidth: integer read fBorderWidth write SetBorderWidth;
    property DrawBorder: boolean read fDrawBorder write SetDrawBorder;
    property Selected: boolean read fSelected write SetSelected;
    property Transparent: boolean read fTransparent write SetTransparent;
    property TransparentColor: TColor read fTransparentColor write SetTransparentColor;
  end;

////////////////////////////////////////////////////////////////////////////////
// EdFormMgr - Editor Form Manager
////////////////////////////////////////////////////////////////////////////////
 // CTI = Convert To Integer
 // ctiValid, value has been converted no change
 // ctiModified, value has been converted but rounded off
 // ctiError, value could not be converted to integer
 TctiResult = (ctiValid, ctiModified, ctiError);

  TFormPanel = class(TAlphaPanel)
  private
    fEdFormNode: TEdFormNode;
    fWindowMode: string;
    procedure SetWindowMode(const Value: string);
  protected
    procedure WM(var msg: TWMWindowPosChanging); message WM_WINDOWPOSCHANGING;
  public
    PopUpX: integer;
    PopUpY: integer;
    VisualPanelList: TVisualPanelList;
    constructor Create(aOwner: TComponent; EdFormNode: TEdFormNode; CreateSubNodes: boolean = true); reintroduce;
    destructor Destroy; override;
    procedure Paint; override;
    procedure UpdateLocation;
  published
    property Node: TEdFormNode read fEdFormNode;
    property WindowMode: string read fWindowMode write SetWindowMode;
  end;

 TedFormMgr = Class(TObject)
 private
  fList: Array of TFormPanel;
  fParent: Tobject;
  function GET_FORM(Index: Integer): TFormPanel;
  procedure SET_FORM(Index: Integer; const Value: TFormPanel);
  function GETCOUNT: Cardinal;
 public
  Constructor Create(aOwner: TObject);
  Destructor Destroy; override;
  Property edForm[Index: Integer]: TFormPanel read GET_FORM write SET_FORM; default;
 published
  property Parent: Tobject read fParent;
  Procedure Delete(Node: TEdFormNode);
  Property Count: Cardinal read GETCOUNT;
  Function Add(OwnerForm: TForm; Node: TEdFormNode; CreateSubNodes: boolean = true): TFormPanel;
 end;

////////////////////////////////////////////////////////////////////////////////
// EdVisuals - Editor Visual panels
////////////////////////////////////////////////////////////////////////////////
  TVisualPanel = Class(TAlphaPanel)
  private
    fConfigString: string;
    fResult: TResult;
    procedure SetConfigString(const Value: string);
    procedure SetResult(const Value: TResult);
  protected
    procedure WM(var msg: TWMWindowPosChanging); message WM_WINDOWPOSCHANGING;
  public
    VO: TVisObject;
    VisualNode: TEdVisualNode;
    constructor Create(aParent: TWinControl; VisPlugIn: TGUID; VisNode: TEdVisualNode; X, Y: integer; CreateSubNodes: boolean = true); reintroduce;
    destructor Destroy; override;
    procedure Paint; override;
    procedure ReInitDimensions;
    procedure UpdateParameters;
    procedure UpdateLocation;
  published
    property ConfigString: string read fConfigString write SetConfigString;
    property Result: TResult read fResult write SetResult;
  end;

  TVisualPanelList = Class(TObject)
  private
   fList: Array of TVisualPanel;
   fOwner: TFormPanel;
   function GET_COUNT: Integer;
   function GET_VISUALPANEL(Index: Integer): TVisualPanel;
   procedure SET_VISUALPANEL(Index: Integer; const Value: TVisualPanel);
  public
   Constructor Create(Owner: TFormPanel);
   Property Owner: TFormPanel read fOwner;
   Function Add(GUID: TGUID; VisualNode: TEdVisualNode; X, Y: Integer; CreateSubNodes: Boolean = true): TVisualPanel;
   Property Count: Integer read GET_COUNT;
   Property VisualPanel[Index: Integer]: TVisualPanel read GET_VISUALPANEL write SET_VISUALPANEL; default;
   Procedure Delete(Node: TEdVisualNode);
  end;

////////////////////////////////////////////////////////////////////////////////
// Tree_Main - Tree Container
////////////////////////////////////////////////////////////////////////////////
 TedCM2Tree = Class(TObject)
 private
  fRoot: TEdRawNode;
  fEdFormMgr: TEdFormMgr;
  fParentForm: TForm;
  fGenPlugInList: PGenPlugInList;
  fVisPlugInList: PVisPlugInList;
  fCMLXPlugInList: PCMLXPlugInList;
  fModified: boolean;
  fOnModified: TNotifyEvent;
  function GetVarMgr: PVarMgr;
    procedure SetModified(const Value: boolean);
 public
  Constructor Create(ParentForm: TForm; EdFormMgr: TEdFormMgr; GenPlugInList: PGenPlugInList; VisPlugInList: PVisPlugInList; CMLXPlugInList: PCMLXPlugInList);
  Destructor Destroy; override;
  Function GetDisplayNode: TEdDisplayNode;
  Property Root: TEdRawNode read fRoot write fRoot;
  Property VarMgr: PVarMgr read GetVarMgr;
  Property EdFormMgr: TEdFormMgr read fEdFormMgr;
  Property GenPlugInList: PGenPlugInList read fGenPlugInList;
  Property VisPlugInList: PVisPlugInList read fVisPlugInList;
  Property CMLXPlugInList: PCMLXPlugInList read fCMLXPlugInList;
  Property ParentForm: TForm read fParentForm;
  Property Modified: boolean read fModified write SetModified;
  Property OnModified: TNotifyEvent read fOnModified write fOnModified;
 end;
////////////////////////////////////////////////////////////////////////////////
// Tree_Nodes - Various tree nodes
////////////////////////////////////////////////////////////////////////////////

 // Node type is used so typecasting can be kept to a minimum, since typecasting
 // cost alot of cpu
 TNodeType = (ntUndefined, ntForm, ntCML, ntDisplay, ntVisual, ntSensor, ntText,
              ntParameterBlock, ntParameter, ntContent, ntEvents, ntEventHandler,
              ntAction, ntExecute, ntReserved, ntComment, ntCMLx, nt_Text, ntLinked);
              // ntText is the <text>something</text>
              // nt_Text is just a plain text value, used for parameters
 // Declare Attrib list, used for nodes attributes
 TAttrib = Record
  Name: String;
  Value: String;
 End;
 TAttribList = Class(TObject)
 Private
  fList: Array of TAttrib;
  function GetCount: Integer;
  function GetAttrib(Index: Integer): TAttrib;
  procedure SetAttrib(Index: Integer; const Value: TAttrib);
 public
  Procedure Add(Name, Value: String);
  Procedure Delete(Index: Integer);
  Property Count: Integer read GetCount;
  Property Attrib[Index: Integer]: TAttrib read GetAttrib write SetAttrib; default;
  Procedure Clear;
 end;
 // Node declerations
 PEdRawNode = ^TEdRawNode;
 TedRawNode = Class(TObject)
 // Rawnode is the main node type, all polymorphic nodes are derived from this
 // it's also used when an unknown node is to be created
 private
  fNodes: Array of TEdRawNode;
  fParent: TEdRawNode;
  fNodeType: TNodeType;
  fName: String;
  fOwner: TEdCM2Tree;
  fChildrenCreated: Boolean;
  function GetCount: Integer;
  function Get_Node(Index: Integer): TEdRawNode;
  procedure Set_Node(Index: Integer; const Value: TEdRawNode);
    function GETINDEX: Integer;
 public
  Attribs: TAttribList;
  ResolvedParameters: TAttribList;
  Result: TResult;
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Destructor Destroy; Override;
  Procedure Resolve; virtual;
  Procedure ResolveChildren; virtual;
  Function ResolveParameters: String; virtual;
  Procedure OnEvent(Sender: TObject); virtual;
  Procedure OnParameterUpdate(Sender: TObject); virtual;
  procedure LoadChildren(SelfNode: IXMLNode; CM2Parent: TEdRawNode); virtual;
  procedure LoadSelf(FromNode: IXMLNode); virtual;
  procedure SetupSelf(); Virtual;
  procedure DismantleSelf; virtual;
  Function Add(NodeType: TNodeType): TEdRawNode;
  Procedure Delete(Index: Integer);
  Property Node[Index: Integer]: TEdRawNode read Get_Node write Set_Node; Default;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; virtual;
  Function ContainsASensorChildNode: Boolean;
  Procedure DeleteSelf;
  Procedure MoveChild(FromIndex, ToIndex: Integer);
  Function Insert(AtIndex: Integer; NodeType: TNodeType): TEdRawNode;
  Function PointerToSelf: PEdRawNode;
  Procedure UpdateParameter(ParamName: String; NewValue: string);
 published
  Property Name: String read fName write fName;
  Property Count: Integer read GetCount;
  Property Owner: TEdCM2Tree read fOwner;
  Property Parent: TEdRawNode read fParent;
  Property NodeType: TNodeType read fNodeType;
  Property ChildrenCreated: Boolean read fChildrenCreated write fChildrenCreated;
  Property Index: Integer read GETINDEX;
 end;
 TEdRawNodes = array of TEdRawNode;

 TEdFormNode = Class(TEdRawNode)
 private
  fFormName: String;
 public
  Form: TFormPanel;
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Procedure SetupSelf(X, Y: Integer); reintroduce;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Procedure DismantleSelf; override;
  Function AddVisualNode(GUID: TGUID; X, Y: Integer): TEdVisualNode;
  Property FormName: String read fFormName write fFormName;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): string; override;
  Function GetParameterBlockNode: TEdParameterBlockNode;
  Function GetContentNode: TEdContentNode;
  Procedure ReloadParameters;
  Procedure SetParameter(Parameter: String; Value: String);
  Procedure OnParameterUpdate(Sender: TObject); override;
  Function GetEventsNode(CanCreate: Boolean): TEdEventsNode;
 end;
 PEdFormNode = ^TEdFormNode;

 TEdVisualNode = Class(TEdRawNode)
 private
  LastFormNode: TEdFormNode;
  fVisualName: string;
  fVisualID: String;
  fVisualGUID: TGuid;
 public
  Visual: TVisualPanel;
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Procedure SetupSelf(GUID: TGUID; X, Y: Integer); reintroduce;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Procedure DismantleSelf; override;
  Function AddSensorNode(PlugInName, SensorName, UpdateStr: String; Dedicated: Boolean): TEdSensorNode;
  Procedure ResolveChildren; override;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Property VisualName: string read fVisualName write fVisualName;
  Property VisualID: String read fVisualID write fVisualID;
  Property VisualGUID: TGuid read fVisualGUID write fVisualGUID;
  Function GetParameterBlockNode: TEdParameterBlockNode;
  Function GetContentNode: TEdContentNode;
  Function GetEventsNode(CanCreate: Boolean): TEdEventsNode;
  Procedure OnEvent(Sender: TObject); override;
  Procedure OnParameterUpdate(Sender: TObject); override;
 end;

 TEdCMLNode = Class(TEdRawNode)
 private
  fCMLVersion: String;
 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Property CMLVersion: String read fCMLVersion write fCMLVersion;
  Procedure SetupSelf(Version: String); reintroduce;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
 end;

 TEdParameterBlockNode = class(TEdRawNode)
 private

 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Procedure AddParameterNode(PName, PValue: String);
  Function ResolveParameters: String; override;
  Procedure SetupSelf(); override;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Procedure OnEvent(Sender: TObject); Override;
  Procedure ChangeParameterValue(ParameterName, ParameterValue: string);
  Function GetParameterNode(ParameterName: String): TEdParameterNode;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
 end;

 TedContentNode = Class(TEdRawNode)
 private

 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Procedure SetupSelf(); override;
 end;

 TedDisplayNode = Class(TEdRawNode)
 private

 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function AddFormNode(X: Integer = -1; Y: Integer = -1): TEdFormNode;
  Procedure SetupSelf(); override;
 end;

 TObjectInspectorFieldType = (oitUnknown, oitFixed, oitColor, oitFont, oitRanged, oitFile, oitInteger);
 TEdParameterNode = Class(TEdRawNode)
 private
  fOITString: String;
  fOIType: TObjectinspectorFieldType;
  fValue: String;
 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Property OIType: TObjectinspectorFieldType read fOIType write fOIType; { Used to control the fieldtype in the OI }
  Property OITString: String read fOITString write fOITString;
  Property Value: String read fValue write fValue;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Function ResolveParameters: String; override;
  procedure LoadSelf(FromNode: IXMLNode); override;
 end;

 TEdTextNode = Class(TEdRawNode)
 private
  fTextValue: String;
 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Property TextValue: String read fTextValue write fTextValue;
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure Resolve; override;
  Procedure SetupSelf(Text: String); reintroduce;
  procedure LoadSelf(FromNode: IXMLNode); override;
 end;

 TEdSensorNode = Class(TEdRawNode)
 private
  fDedicated: Boolean;
  fUpdate: String;
  fSensorName: String;
  fPlugInName: String;
  fID: String;
  VarItem: pVarItem;
  GenPlugIn: PGenPlugIn;
  fSafeModeName: String;
 public
  PlugInGUID: TGUID;
  SensorIndex: Integer;
  LinkedTo: TEdSensorNode;
  Procedure UpdateGenPlugIn;
  Procedure UpdateVarItem;
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure SetupSelf(PlugName, PlugSensor: String; Update: String; Dedicated: Boolean; ID: String); reintroduce;
  Procedure SetupSelfFromLinked(ID: String); 
  Function ResolveParameters: String; override;
  Function GetParameterBlockNode: TEdParameterBlockNode;
  Procedure UpdateParameters;
  Procedure Resolve; override;
  Procedure DismantleSelf; override;
  Property UpdateStr: String read fUpdate write fUpdate;
  Property IsDedicated: Boolean read fDedicated write fDedicated;
  Property IDName: String read fID write fID;
  Property PlugInName: String read fPlugInName write fPlugInName;
  Property SensorName: String read fSensorName write fSensorName;
  Property SafeModeName: String read fSafeModeName;
  Procedure OnEvent(Sender: TObject); override;
  Procedure OnParameterUpdate(Sender: TObject); override;
  Procedure RebuildVarItem;
  procedure LoadSelf(FromNode: IXMLNode); override;
 end;

 TEdEventsNode = Class(TEdRawNode)
 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Function GetEventHandlerNode(Trigger: String; CanCreate: Boolean): TEdEventHandlerNode;
  Function GetEvents: String;
  Procedure DeleteEvent(Trigger, Mods, Command: String);
 end;

 TEdEventHandlerNode = Class(TEdRawNode)
 private
  fTrigger: String;
 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure SetupSelf(TriggerStr: String); reintroduce;
  Property Trigger: String read fTrigger;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Function GetActionNode(Mods: String; CanCreate: Boolean): TEdActionNode;
 end;

 TEdActionNode = Class(TEdRawNode)
 private
  fModKeys: String;
 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure SetupSelf(ModKeyStr: String); reintroduce;
  Property ModKeys: String read fModKeys;
  procedure LoadSelf(FromNode: IXMLNode); override;
  Function GetExecuteNode(Execute: String; CanCreate: Boolean): TEdExecuteNode;
 end;

 TEdExecuteNode = Class(TEdRawNode)
 private
    fExecute: String;

 public
  constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure SetupSelf(Execute: String); reintroduce;
  Property Execute: String read fExecute;
  Procedure LoadSelf(FromNode: IXMLNode); override;
 end;

 TEdCommentNode = Class(TEdRawNode)
 private
    fCommentStr: String;

 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
  Procedure SetupSelf(CommentString: String); reintroduce;
  Procedure LoadSelf(FromNode: IXMLNode); override;
  Property CommentStr: String read fCommentStr write fCommentStr;
 end;

 TEdRootNode = Class(TEdRawNode)
 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
  Function WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String; override;
 end;
 TEdLinkedNode = Class(TEdRawNode)
 public
  Constructor Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
 end;

 Procedure DetermineParentTypeAndCallEvent(Source: TEdRawNode);
 Procedure DetermineChildTypeAndCallDismantle(Source: TEdRawNode; ChildIndex: Integer);
 Function NodeTypeToString(NT: TNodeType): string;
 Function ConvertToInteger(Str: String; Var Value: Integer): TctiResult;


Var
 SensorObjCount: Cardinal = 0;

 {
 MSSING TAGS

 ntComment,


 ntEvents,
 ntEventHandler,
 ntAction,
 ntExecute,
 ntReserved,
 ntCMLx,
 nt_Text
 }

////////////////////////////////////////////////////////////////////////////////
// End Declaretion
////////////////////////////////////////////////////////////////////////////////

implementation

Uses
 Main,  Types,
 Panels_Shared, Dialogs, filter, AddWin, Math, VarWin, SnapTo,
  ProcsAndTypes, Objbrowsewin;

Function ConvertToInteger(Str: String; Var Value: Integer): TCtiResult;
Var
 R: Real;
begin
 Result := ctiValid;
 try
  Value := StrToInt(Str);
 except
  on EConvertError do begin
   try
    R := StrToFloat(Str);
    Value := Round(R);
    Result := ctiModified;
   except
    on EConvertError do begin
     Result := ctiError;
    end;
   end;
  end;
 end;
end;

Procedure DetermineChildTypeAndCallDismantle(Source: TEdRawNode; ChildIndex: Integer);
begin
 // Must typecast prior to event... and work with all these
 { ntUndefined, ntCML, ntDisplay, ntText,
              ntParameter, ntEvents, ntEventHandler,
              ntAction, ntExecute, ntReserved, ntComment, ntCMLx, nt_Text
 }
 If (ChildIndex < 0) or (ChildIndex > Source.Count - 1) then exit;
 Case Source.Node[ChildIndex].NodeType of
  ntSensor: (Source.Node[ChildIndex] as TEdSensorNode).DismantleSelf;
  ntVisual: (Source.Node[ChildIndex] as TEdVisualNode).DismantleSelf;
  ntForm: (Source.Node[ChildIndex] as TEdFormNode).DismantleSelf;
  ntContent: (Source.Node[ChildIndex] as TEdContentNode).DismantleSelf;
  ntParameterBlock: (Source.Node[ChildIndex] as TEdParameterBlockNode).DismantleSelf;
  ntParameter: (Source.Node[ChildIndex] as TEdParameterNode).DismantleSelf;
  else Source.Node[ChildIndex].DismantleSelf;
 end;
end;

Procedure DetermineParentTypeAndCallEvent(Source: TEdRawNode);
begin
 // Must typecast prior to event... and work with all these
 { ntUndefined, ntCML, ntDisplay, ntText,
              ntParameter, ntEvents, ntEventHandler,
              ntAction, ntExecute, ntReserved, ntComment, ntCMLx, nt_Text
 }
 If Source.Parent = nil then exit;
 Case Source.Parent.NodeType of
  ntSensor: (Source.Parent as TEdSensorNode).OnEvent(Source);
  ntVisual: (Source.Parent as TEdVisualNode).OnEvent(Source);
  ntForm: (Source.Parent as TEdFormNode).OnEvent(Source);
  ntContent: (Source.Parent as TEdContentNode).OnEvent(Source);
  ntParameterBlock: (Source.Parent as TEdParameterBlockNode).OnEvent(Source);
  ntParameter: (Source.Parent as TEdParameterNode).OnEvent(Source);
 end;
end;

Function NodeTypeToString(NT: TNodeType): string;
begin
 Case NT of
  ntForm: Result := 'Form';
  ntCML: Result := 'CML';
  ntDisplay: Result := 'Display';
  ntVisual: Result := 'Visual';
  ntSensor: Result := 'Sensor';
  ntText: Result := 'Text';
  ntParameterBlock: Result := 'ParameterBlock';
  ntParameter: Result := 'Parameter';
  ntContent: Result := 'Content';
  ntEvents: Result := 'Events';
  ntEventHandler: Result := 'EventHandler';
  ntAction: Result := 'Action';
  ntExecute: Result := 'Execute';
  ntReserved: Result := 'Reserved';
  ntComment: Result := 'Comment';
  ntCMLx: Result := 'CMLx';
  nt_Text: Result := '#text';
  else Result := 'Undefined';
 end;
end;

////////////////////////////////////////////////////////////////////////////////
//Base class for TFormPanel and TVisualPanel
////////////////////////////////////////////////////////////////////////////////
{ TAlphaPanel }
procedure TAlphaPanel.DoDragDrop(Sender, Source: TObject; X, Y: integer);
var
  GUID: TGUID;
  S, ID: String;
  SN: TEdSensorNode;
begin
  if (Source is TListView) then
    begin
      if (Sender is TFormPanel) then
        begin
          if Uppercase((Source as TListView).Name) = 'LISTVIS' then
            begin
              GUID := StringToGUID((Source as TListView).Selected.SubItems[0]);
              (Sender as TFormPanel).Node.AddVisualNode(GUID, X, Y);
            end;
        end
      else
        begin
          if Uppercase((Source as TListView).Name) = 'LISTGEN' then
            begin
              GUID := StringToGUID((Source as TListView).Selected.SubItems[0]);
              AddSensorDlg.Execute(GUID, (Sender as TVisualPanel).VisualNode);
            end;
          if uppercase((Source as TListView).Name) = 'VARWINLISTVIEW' then
            begin
              ID := (Source as TListView).Selected.SubItems[0];
              if ID = '' then
                begin
                  S := '';
                  if not InputQuery('ID Missing for dragged sensor', 'To Proceed you will have to provide an ID name for the sensor (If left empty operation will be cancelled) - ID ?', S) then exit;
                  if S = '' then exit;
                  SN := (oeMain.VarMgr[(Source as TListView).Selected.Index].Nodes[0] as TEdSensorNode);
                  SN.IDName := S;
                  SN.OnParameterUpdate(self);
                  SN.OnEvent(self);
                  ID := S;
                end;
              ((Sender as TVisualPanel).VisualNode.GetContentNode.Add(ntSensor) as TEdSensorNode).SetupSelfFromLinked(ID);
              objBrowser.LoadFromNode();
            end;
        end;
    end;
end;

procedure TAlphaPanel.DoDragOver(Sender, Source: TObject; X, Y: integer; State: TDragState; var Accept: boolean);
begin
  Accept := False;
  if (Source is TListview) then
    begin
      if (Sender is TFormPanel) then
        begin
          if uppercase((Source as TListView).Name) = 'LISTVIS' then Accept := true;
        end
      else
        begin
          if uppercase((Source as TListView).Name) = 'LISTGEN' then Accept := true;
          if uppercase((Source as TListView).Name) = 'VARWINLISTVIEW' then Accept := true;
        end;
    end;
end;

procedure TAlphaPanel.DoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
  case Button of
    mbLeft: begin
              BringToFront;
              if (ssCtrl in Shift) then
                begin
                  if (Sender is TFormPanel) then oeMain.AddToSelected((Sender as TFormPanel).fEdFormNode)
                    else oeMain.AddToSelected((Sender as TVisualPanel).VisualNode);
                  Exit;
                end
              else if (Sender is TFormPanel) then oeMain.SelectNode((Sender as TFormPanel).fEdFormNode)
                     else oeMain.SelectNode((Sender as TVisualPanel).VisualNode);
              MDown(Sender, X, Y);
              if (Sender is TFormPanel) then (Sender as TFormPanel).UpdateLocation
                else (Sender as TVisualPanel).UpdateLocation;
            end;
  end;
  inherited;
end;

procedure TAlphaPanel.DoMouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
begin
  MMove(Self, X, Y);
  // Paint when thread is free
  // Invalidate;
  if (Sender is TFormPanel) then
    oeMain.Statusbar1.Panels[1].Text := 'Cursor Coordinates ' +
    '[Desktop: X = ' + IntToStr(Left + X) + ', Y = ' + IntToStr(Top + Y) + '] ' +
    '[Form: X = ' + IntToStr(X) + ', Y = ' + IntToStr(Y) + ']'
  else
    oeMain.Statusbar1.Panels[1].Text := 'Cursor Coordinates ' +
    '[Desktop: X = ' + IntToStr(Parent.Left + Left + X) + ', Y = ' + IntToStr(Parent.Top + Top + Y) + '] ' +
    '[Form: X = ' + IntToStr(Left + X) + ', Y = ' + IntToStr(Left + Y) + '] ' +
    '[VO: X = ' + IntToStr(X) + ', Y = ' + IntToStr(Y) + ']';
end;

procedure TAlphaPanel.DoMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
  case Button of
    mbRight: begin
               if (Sender is TFormPanel) then
                 begin
                   (Sender as TFormPanel).PopupX := X;
                   (Sender as TFormPanel).PopupY := Y;
                   oeMain.SelectNode((Sender as TFormPanel).Node);
                   oeMain.Popup_FormPanels.Popup((Sender as TFormPanel).ClientOrigin.X + X, (Sender as TFormPanel).ClientOrigin.Y + Y);
                 end
               else
                 begin
                   oeMain.SelectNode((Sender as TVisualPanel).VisualNode);
                   oeMain.Popup_Visuals.Popup((Sender as TVisualPanel).ClientOrigin.X + X, (Sender as TVisualPanel).ClientOrigin.Y + Y);
                 end;
             end;
  end;
end;

procedure TAlphaPanel.SetAlpha(const Value: byte);
begin
  fAlpha := Value;
  Repaint;
end;

procedure TAlphaPanel.SetBorderColor(const Value: TColor);
begin
  fBorderColor := Value;
  Repaint;
end;

procedure TAlphaPanel.SetBorderWidth(const Value: integer);
begin
  fBorderWidth := Value;
  Repaint;
end;

procedure TAlphaPanel.SetDrawBorder(const Value: boolean);
begin
  fDrawBorder := Value;
  Repaint;
end;

procedure TAlphaPanel.SetSelected(const Value: boolean);
begin
  fSelected := Value;
  Repaint;
end;

procedure TAlphaPanel.SetTransparent(const Value: boolean);
begin
  fTransparent := Value;
  Repaint;
end;

procedure TAlphaPanel.SetTransparentColor(const Value: TColor);
begin
  fTransparentColor := Value;
  Repaint;
end;

////////////////////////////////////////////////////////////////////////////////
// EdFormMgr - Editor Form Manager
////////////////////////////////////////////////////////////////////////////////

{ TedFormMgr }
function TedFormMgr.Add(OwnerForm: TForm; Node: TEdFormNode; CreateSubNodes: boolean = true): TFormPanel;
Var
 I: Integer;
 Sel: Integer;
begin
 Sel := -1;
 For I := 0 to count - 1 do begin
  If fList[I] = nil then Sel := I;
 end;
 If Sel = -1 then begin
  SetLength(fList, length(fList) + 1);
  Sel := Length(fList) -1;
 end;
 fList[Sel] := TFormPanel.Create(OwnerForm, Node, CreateSubNodes);
 fList[Sel].Parent := oeMain.ScrollBox1;
 Result := fList[Sel];
end;

constructor TedFormMgr.Create(aOwner: TObject);
begin
 fParent := aOwner;
end;

procedure TedFormMgr.Delete(Node: TEdFormNode);
Var
 Sel, I: Integer;
begin
 Sel := -1;
 For I := 0 to count - 1 do begin
  If assigned(fList[I]) then begin
   If Node = fList[I].fEdFormNode then Sel := I;
  end;
 end;
 If Sel = -1 then exit;
 FreeAndNil(fList[Sel]);
 oeMain.Update;
end;

destructor TedFormMgr.Destroy;
begin

  inherited;
end;

function TedFormMgr.GETCOUNT: Cardinal;
begin
 Result := length(fList);
end;

function TedFormMgr.GET_FORM(Index: Integer): TFormPanel;
begin
 Result := fList[Index];
end;

procedure TedFormMgr.SET_FORM(Index: Integer; const Value: TFormPanel);
begin
 fList[Index] := Value;
end;

{------------------------------------------------------------------------------}

{ TFormPanel }
constructor TFormPanel.Create(aOwner: TComponent; EdFormNode: TEdFormNode; CreateSubNodes: Boolean = true);
begin
  inherited Create(oeMain.ScrollBox1);
  fEdFormNode := EdFormNode;
  DoubleBuffered := true;
  VisualPanelList := TVisualPanelList.Create(self);
  Parent := oeMain.ScrollBox1;
  ParentColor := false;
  ParentBackground := False;
  Visible := false;
  PopupMenu := oeMain.Popup_FormPanels;
  Caption := '';
  Alpha := 255;
  Color := clWhite;
  BorderColor := clRed;
  BorderWidth := 0;
  DrawBorder := true;
  Transparent := false;
  TransparentColor := clFuchsia;
  OnDragOver := DoDragOver;
  OnDragDrop := DoDragDrop;
  OnMouseDown := DoMouseDown;
  OnMouseMove := DoMouseMove;
  OnMouseUp := DoMouseUp;
end;

destructor TFormPanel.Destroy;
begin
  FreeAndNil(VisualPanelList);
  inherited;
end;

procedure TFormPanel.WM(var msg: TWMWindowPosChanging);
begin
  if not options.Visual.ShowGrid then exit;
  if Options.Visual.SnapToGrid then SnapTo.SnapToGrid(MSG, Self);
end;

procedure TFormPanel.Paint;
var
  gridx, gridy, i, X, Y: Integer;
  bmpSrc, bmpDest: TBitmap;
  aRect: TRect;
  bf: BLENDFUNCTION;
begin
  inherited;
  with aRect do
    begin
      Left := Self.Left - oeMain.DesktopImage1.Left;
      Top := Self.Top - oeMain.DesktopImage1.Top;
      Right := Left + Self.Width;
      Bottom := Top + Self.Height;
    end;
  if (Transparent) and (TransparentColor = Color) then Canvas.CopyRect(ClientRect, oeMain.DesktopImage1.Canvas, aRect)
    else
      begin
        with bf do
          begin
            BlendOp := AC_SRC_OVER;
            BlendFlags := 0;
            SourceConstantAlpha := Alpha;
            AlphaFormat := 0;
          end;
        bmpSrc := TBitmap.Create;
        with bmpSrc do
          begin
            PixelFormat := pf32bit;
            Width := Self.Width;
            Height := Self.Height;
            Canvas.Pen.Color := Self.Color;
            Canvas.Brush.Color := Self.Color;
            Canvas.Rectangle(Canvas.ClipRect);
          end;
        bmpDest := TBitmap.Create;
        with bmpDest do
          begin
            PixelFormat := pf32bit;
            Width := Self.Width;
            Height := Self.Height;
          end;
        bmpDest.Canvas.CopyRect(bmpDest.Canvas.ClipRect, oeMain.DesktopImage1.Canvas, aRect);
        AlphaBlend(bmpDest.Canvas.Handle, 0, 0, Width, Height, bmpSrc.Canvas.Handle, 0, 0, Width, Height, bf);
        Canvas.Draw(0, 0, bmpDest);
        FreeAndNil(bmpSrc);
        FreeAndNil(bmpDest);
      end;
  if options.Visual.FormPanelOptions.showgrid then
    begin
      canvas.Pen.Color := options.Visual.FormPanelOptions.GridColor;
      canvas.pen.width := 1;
      gridX := Width div options.Visual.FormPanelOptions.GridSpacingX;
      gridY := Height div options.Visual.FormPanelOptions.GridSpacingY;
      if options.Visual.FormPanelOptions.ShowDot then
        begin
          for X := 0 to gridX do
            begin
              For Y := 0 to GridY do
                begin
                  Canvas.MoveTo(X * Options.Visual.FormPanelOptions.GridSpacingX, Y * Options.Visual.FormPanelOptions.GridSpacingY);
                  Canvas.LineTo((X * Options.Visual.FormPanelOptions.GridSpacingX) + 1, (Y * Options.Visual.FormPanelOptions.GridSpacingY));
                end;
            end;
        end
      else
        begin
          for i := 0 to gridX do
            begin
              Canvas.MoveTo(i * Options.Visual.FormPanelOptions.GridSpacingX, 0);
              Canvas.LineTo(i * Options.Visual.FormPanelOptions.GridSpacingX, Height);
            end;
          for i := 0 to gridY do
            begin
              Canvas.MoveTo(0, i * Options.Visual.FormPanelOptions.GridSpacingY);
              Canvas.LineTo(Width, i * Options.Visual.FormPanelOptions.GridSpacingY);
            end;
        end;
    end;
  if Selected then begin
   Canvas.Pen.Color := clred;
   Canvas.Pen.Width := 3;
   Canvas.Brush.Style := bsClear;
   Canvas.Rectangle(0, 0, width, height);
  end
   else
  begin
   // if transparent and not selected we still need to add a small outline
   if transparent then begin
    Canvas.Pen.Color := clyellow;
    Canvas.Pen.Width := 1;
    Canvas.Brush.Style := bsClear;
    Canvas.Rectangle(0, 0, width, height);
   end;
  end;
end;

procedure TFormPanel.SetWindowMode(const Value: string);
begin
  fWindowMode := Value;
  Repaint;
end;

procedure TFormPanel.UpdateLocation;
Var
 I: Integer;
 PNode: TEdParameterBlockNode;
 PANode: TEdParameterNode;
begin
 PNode := Node.GetParameterBlockNode;
 For I := 0 to PNode.count - 1 do begin
  If PNode[I].NodeType = ntParameter then begin
   PANode := (PNode[I] as TEdParameterNode);
   If uppercase(PANode.Name) = 'RIGHT' then begin
    PANode.Value := IntToStr((oeMain.DesktopImage1.Width - (left + width)) + (oeMain.DesktopImage1.Left));
   end;
   If uppercase(PANode.Name) = 'BOTTOM' Then begin
    PANode.Value := IntToStr((oeMain.DesktopImage1.Height - (top + height)) + (oeMain.DesktopImage1.Top));
   end;
   If uppercase(PANode.Name) = 'LEFT' then begin
    PANode.Value := IntToStr(Left + (oeMain.DesktopImage1.Left * -1));
   end;
   If uppercase(PANode.Name) = 'TOP' then begin
    PANode.Value := IntToStr(Top + (oeMain.DesktopImage1.Top * -1));
   end;
   If uppercase(PANode.Name) = 'HEIGHT' then begin
    PANode.Value := IntToStr(Height);
   end;
   If uppercase(PANode.Name) = 'WIDTH' then begin
    PANode.Value := IntToStr(Width);
   end;
  end;
 end;
 Node.Owner.Modified := true;
 oeMain.SelectNode(Node, true);
end;

////////////////////////////////////////////////////////////////////////////////
// EdVisuals - Editor Visual panels
////////////////////////////////////////////////////////////////////////////////

{ TVisualPanel }
constructor TVisualPanel.Create(aParent: TWinControl; VisPlugIn: TGUID; VisNode: TEdVisualNode; X, Y: integer; CreateSubNodes: boolean = true);
var
  Res: TResult;
  Sel, i: integer;
  ParamNode: TEdParameterBlockNode;
begin
  inherited create(aParent);
  Canvas.Brush.Style := bsClear;
  ParentColor := false;
  ParentBackground := False;
  Parent := aParent;
  VisualNode := VisNode;
  fSelected := false;
  Left := X;
  Top := Y;
  // OPTION LINK IN - default size

  //don't uncomment these, setting Height causes errors (don't know why, it just does)
  //Height := 100;
  //Width := 100;

  Visible := true;
  PopupMenu := oeMain.Popup_Visuals;
  //BevelOuter := bvNone;
  //*
  Color := clWhite;
  //*
  OnDragOver := DoDragOver;
  OnDragDrop := DoDragDrop;
  OnMouseDown := DoMouseDown;
  OnMouseUp := DoMouseUp;
  OnMouseMove := DoMouseMove;
  VO := TVisObject.Create(VisPlugIn, self.Handle, nil);
  Res.SensorStyle := ssNumeric;
  Res.Value := 25;
  BorderWidth := 5;
  VO.SetResult(Res);
  if not CreateSubNodes then exit;
  ReInitDimensions;
  ParamNode := (VisNode.Add(ntParameterBlock) as TEdParameterBlockNode);
  VisNode.Add(ntContent);
  ParamNode.AddParameterNode('Left', IntToStr(X));
  ParamNode.AddParameterNode('Top', IntToStr(Y));
  ParamNode.AddParameterNode('Width', IntToStr(100));
  ParamNode.AddParameterNode('Height', InttOstr(100));
  Sel := oeMain.VESL.GetIndexByName(VO.Name);
  for i := 0 to oeMain.VESL[Sel].Count - 1 do ParamNode.AddParameterNode(oeMain.VESL[Sel][i].Name, oeMain.VESL[Sel][i].Value);
  UpdateParameters;
end;

destructor TVisualPanel.Destroy;
begin

  inherited;
end;

procedure TVisualPanel.Paint;
var
  bmpSrc, bmpDest: TBitmap;
  aRect: TRect;
  bf: BLENDFUNCTION;
begin
  inherited;
  ReInitDimensions;
  if Assigned(VO) then
    begin
      VO.Paint;
      bmpSrc := TBitmap.Create;
      bmpSrc.Width := self.Width;
      bmpSrc.Height := self.Height;
      bmpSrc.Canvas.CopyRect(bmpSrc.Canvas.ClipRect, self.Canvas, self.Canvas.ClipRect);
      with bf do
        begin
          BlendOp := AC_SRC_OVER;
          BlendFlags := 0;
          SourceConstantAlpha := (Parent as TFormPanel).Alpha;
          AlphaFormat := 0;
        end;
      bmpDest := TBitmap.Create;
      bmpDest.PixelFormat := pf32bit;
      bmpDest.Width := Width;
      bmpDest.Height := Height;
      if (Parent as TFormPanel).Transparent then
        begin
          bmpDest.TransparentColor := (Parent as TFormPanel).TransparentColor;
          bmpDest.Transparent := true;
        end;
      with aRect do
        begin
          Left := (Parent as TFormPanel).Left - oeMain.DesktopImage1.Left + self.Left;
          Top := (Parent as TFormPanel).Top - oeMain.DesktopImage1.Top + self.Top;
          Right := Left + Self.Width;
          Bottom := Top + Self.Height;
        end;
      Canvas.CopyRect(bmpDest.Canvas.ClipRect, oeMain.DesktopImage1.Canvas, aRect);
      AlphaBlend(bmpDest.Canvas.Handle, 0, 0, Width, Height, bmpSrc.Canvas.Handle, 0, 0, Width, Height, bf);
      if (Parent as TFormPanel).Transparent then Canvas.Draw(0, 0, bmpDest)
        else AlphaBlend(self.Canvas.Handle, 0, 0, Width, Height, bmpDest.Canvas.Handle, 0, 0, Width, Height, bf);
      FreeAndNil(bmpSrc);
      FreeAndNil(bmpDest);
    end;
  if Selected then
    begin
      Canvas.Pen.Color := clred;
      Canvas.Pen.Width := 3;
      Canvas.Rectangle(0, 0, width, height);
    end;
end;

procedure TVisualPanel.ReInitDimensions;
begin
  if Focused then
    begin
      VO.Left := 5;
      VO.Top := 5;
      VO.Height := Height - (10);
      VO.Width := Width - (10);
    end
  else
    begin
      VO.Left := 0;
      VO.Top := 0;
      VO.Height := Height;
      VO.Width := Width;
    end;
  VO.SetDC(self.Handle);
end;

procedure TVisualPanel.SetConfigString(const Value: string);
begin
  fConfigString := Value;
  VO.ConfigString := Value;
  Paint;
end;

procedure TVisualPanel.UpdateParameters;
Var
 Str: String;
 Res, I: Integer;
 N, V: String;
 SnapTo: Boolean;
begin
 If Options.Visual.FormPanelOptions.SnapToGrid then begin
  SnapTo := True;
  Options.Visual.FormPanelOptions.SnapToGrid := False;
 end
  else
 begin
  SnapTo := false;
 end;
 Str := VisualNode.GetParameterBlockNode.ResolveParameters;
 For I := 1 to countfilt(Str, '|') do begin
  N := GetFilt(Str, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'LEFT' then begin
   case ConvertToInteger(V, Res) of
    ctiValid: begin
               Left := Res;
              end;
    ctiModified: begin
                  Left := Res;
                  VisualNode.GetParameterBlockNode.ChangeParameterValue('left', IntToStr(Res));
                 end;
    ctiError: begin
               left := 0;
               VisualNode.GetParameterBlockNode.ChangeParameterValue('left', '0');
              end;
   end;
  end;
  If uppercase(N) = 'TOP' then begin
   case ConvertToInteger(V, Res) of
    ctiValid: begin
               Top := Res;
              end;
    ctiModified: begin
                  Top := Res;
                  VisualNode.GetParameterBlockNode.ChangeParameterValue('top', IntToStr(Res));
                 end;
    ctiError: begin
               top := 0;
               VisualNode.GetParameterBlockNode.ChangeParameterValue('top', '0');
              end;
   end;
  end;
  If uppercase(N) = 'WIDTH' then begin
   case ConvertToInteger(V, Res) of
    ctiValid: begin
               Width := Res;
              end;
    ctiModified: begin
                  Width := Res;
                  VisualNode.GetParameterBlockNode.ChangeParameterValue('width', IntToStr(Res));
                 end;
    ctiError: begin
               width := 100;
               VisualNode.GetParameterBlockNode.ChangeParameterValue('width', '100');
              end;
   end;
  end;
  If uppercase(N) = 'HEIGHT' then begin
   case ConvertToInteger(V, Res) of
    ctiValid: begin
               Height := Res;
              end;
    ctiModified: begin
                  Height := Res;
                  VisualNode.GetParameterBlockNode.ChangeParameterValue('height', IntToStr(Res));
                 end;
    ctiError: begin
               left := 100;
               VisualNode.GetParameterBlockNode.ChangeParameterValue('height', '100');
              end;
   end;
  end;
 end;
 VisualNode.Owner.Modified := true;
 ReInitDimensions;
 ConfigString := Str;
 If SnapTo then begin
  Options.Visual.FormPanelOptions.SnapToGrid := true;
 end;
end;

procedure TVisualPanel.SetResult(const Value: TResult);
begin
  fResult := Value;
  VO.SetResult(fResult);
  VO.Paint;
end;

procedure TVisualPanel.WM(var msg: TWMWindowPosChanging);
begin
  if options.Visual.FormPanelOptions.ShowGrid then
    if options.Visual.FormPanelOptions.SnapToGrid then snapto.VisSnapToGrip(msg, self);
end;

procedure TVisualPanel.UpdateLocation;
Var
 I: Integer;
 PNode: TEdParameterBlockNode;
 PANode: TEdParameterNode;
begin
 PNode := VisualNode.GetParameterBlockNode;
 For I := 0 to PNode.count - 1 do begin
  If PNode[I].NodeType = ntParameter then begin
   PANode := (PNode[I] as TEdParameterNode);
   If uppercase(PANode.Name) = 'LEFT' then begin
    PANode.Value := IntToStr(Left);
   end;
   If uppercase(PANode.Name) = 'TOP' then begin
    PANode.Value := IntToStr(Top);
   end;
   If uppercase(PANode.Name) = 'HEIGHT' then begin
    PANode.Value := IntToStr(Height);
   end;
   If uppercase(PANode.Name) = 'WIDTH' then begin
    PANode.Value := IntToStr(Width);
   end;
  end;
 end;
 oeMain.SelectNode(VisualNode, True);
end;

{ TVisualPanelList }

function TVisualPanelList.Add(GUID: TGUID; VisualNode: TEdVisualNode; X, Y: Integer; CreateSubNodes: Boolean = true): TVisualPanel;
Var
 Sel, I: Integer;
begin
 Sel := -1;
 For I := 0 to count - 1 do begin
  If fList[I] = nil then Sel := I;
 end;
 If Sel = -1 then begin
  SetLength(fList, length(fList) + 1);
  Sel := Length(fList) -1;
 end;
 fList[Sel] := TVisualPanel.Create(Owner, GUID, VisualNode, X, Y, CreateSubNodes);
 Result := fList[Sel];
end;

constructor TVisualPanelList.Create(Owner: TFormPanel);
begin
 fOwner := Owner;
end;

procedure TVisualPanelList.Delete(Node: TEdVisualNode);
Var
 Sel, I: Integer;
begin
 Sel := -1;
 For I := 0 to count - 1 do begin
  If assigned(fList[I]) then begin
   If Node = fList[I].VisualNode then Sel := I;
  end;
 end;
 If Sel = -1 then exit;
 FreeAndNil(fList[Sel]);
// For I := Sel to count - 2 do begin
//  fList[I] := fList[I + 1];
// end;
end;

function TVisualPanelList.GET_COUNT: Integer;
begin
 Result := Length(fList);
end;

function TVisualPanelList.GET_VISUALPANEL(Index: Integer): TVisualPanel;
begin
 Result := fList[Index];
end;

procedure TVisualPanelList.SET_VISUALPANEL(Index: Integer;
  const Value: TVisualPanel);
begin
 fList[Index] := Value;
end;

////////////////////////////////////////////////////////////////////////////////
// Tree_Main - Tree Container
////////////////////////////////////////////////////////////////////////////////

{ TEdCM2Tree }

constructor TEdCM2Tree.Create(ParentForm: TForm; EdFormMgr: TEdFormMgr; GenPlugInList: PGenPlugInList;
  VisPlugInList: PVisPlugInList; CMLXPlugInList: PCMLXPlugInList);
begin
 Self.fGenPlugInList := GenPlugInList;
 Self.fVisPlugInList := VisPlugInList;
 Self.fCMLXPlugInList := CMLXPlugInList;
 Self.fEdFormMgr := EdFormMgr;
 Self.fParentForm := ParentForm;
 Self.OnModified := oeMain.OnTreeModified;
// Self.fRoot := TEdRawNode.Create(Self, nil);
end;

destructor TEdCM2Tree.Destroy;
begin
 // Dismantle all nodes - Free up memory
 fRoot.DismantleSelf;
 // Free up the rode, which through chain reaction (recursive) should free up all
 FreeAndNil(fRoot);
 // free up the object by calling inherited
 inherited;
end;

function TedCM2Tree.GetDisplayNode: TEdDisplayNode;
Var
 Sel, sel2, I: Integer;
begin
 Result := nil;
 Sel := -1;
 For I := 0 to fRoot.Count - 1 do begin
  If fRoot[I].NodeType = ntCML then begin
   Sel := I;
   break;
  end;
 end;
 If Sel = -1 then begin
  MessageDlg('Error: Unable to locate CML node'+#13+#10+'Location: TEdCM2Tree.GetDisplayNode', mtError, [mbOK], 0);
  exit;
 end;
 Sel2 := -1;
 For I := 0 to fRoot[Sel].Count - 1 do begin
  If fRoot[Sel][I].NodeType = ntDisplay then begin
   Sel2 := I;
  end;
 end;
 If Sel2 = -1 then begin
  showmessage(fRoot[Sel].WriteXML(''));
  MessageDlg('Error: Unable to locate DISPLAY node'+#13+#10+'Location: TEdCM2Tree.GetDisplayNode', mtError, [mbOK], 0);
  exit;
 end;
 Result := (fRoot.Node[Sel].Node[Sel2] as TEdDisplayNode);
end;

function TedCM2Tree.GetVarMgr: PVarMgr;
begin
 Result := addr(oeMain.VarMgr);
end;

procedure TedCM2Tree.SetModified(const Value: boolean);
begin
 If value = fModified then exit;
 fModified := Value;
 If assigned(OnModified) then OnModified(self);
end;


////////////////////////////////////////////////////////////////////////////////
// Tree_Nodes - Various tree nodes
////////////////////////////////////////////////////////////////////////////////

{ TedRawNode }

function TedRawNode.ContainsASensorChildNode: Boolean;
Var
 I: Integer;
begin
 result := false;
 For I := 0 to Count - 1 do begin
  If Node[I].NodeType = ntSensor then begin
   Result := true;
   exit;
  end;
 end;
end;

function TedRawNode.Add(NodeType: TNodeType): TEdRawNode;
// Add Childnode
Var
 Sel: Integer;
begin
 Sel := Count;
 Setlength(fNodes, count + 1);
 // Determine what type of node is desired and create the accordingly
 Case NodeType of
  ntForm: fNodes[Sel] := TEdFormNode.Create(Owner, Self);
  ntCML: fNodes[Sel] := TEdCMLNode.Create(Owner, Self);
  ntDisplay: fNodes[Sel] := TedDisplayNode.Create(Owner, Self);
  ntVisual: fNodes[Sel] := TEdVisualNode.Create(Owner, Self);
  ntSensor: fNodes[Sel] := TEdSensorNode.Create(Owner, self);
  ntText: fNodes[Sel] := TEdTextNode.Create(Owner, Self);
  ntParameterBlock: fNodes[Sel] := TEdParameterBlockNode.Create(Owner, Self);
  ntParameter: fNodes[Sel] := TEdParameterNode.Create(Owner, Self);
  ntContent: fNodes[Sel] := TEdContentNode.Create(Owner, Self);
  ntEvents: fNodes[Sel] := TEdEventsNode.Create(Owner, Self);
  ntEventHandler: fNodes[Sel] := TEdEventHandlerNode.Create(Owner, Self);
  ntAction: fNodes[Sel] := TEdActionNode.Create(Owner, Self);
  ntExecute: fNodes[Sel] := TEdExecuteNode.Create(Owner, Self);
  ntComment: fNodes[Sel] := TEdCommentNode.Create(Owner, Self);
 else
  // If not found - Create as Undefined (which creates a raw node)
  fNodes[Sel] := TEdRawNode.Create(Owner, Self);
 End;
 Owner.Modified := true;
 Result := fNodes[Sel];
end;

constructor TEdRawNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 // Set "Parent"
 fParent := Parent;
 // Set "Owner"
 fOwner := Owner;
 // Create attrib list
 Attribs := TAttribList.Create;
 // Create resolvedparameter list
 ResolvedParameters := TAttribList.Create;
 // Set the nodetype - since it's a raw node it will be defined as "undefined"
 Self.fNodeType := ntUndefined;
end;

procedure TEdRawNode.Delete(Index: Integer);
// Delete the node at Index
Var
 I: Integer;
begin
 // IF index is a larger number than possible then exit
 If Index >= Self.Count Then Exit;
 // Free memory
 fNodes[Index].DismantleSelf;
 // Reposition the entire array below "Index"
 For I := index to Self.Count - 2 do begin
  fNodes[I] := fNodes[I + 1];
 End;
 Owner.Modified := true;
 // Resize the array to one less
 SetLength(fNodes, Length(fNodes) - 1);
End;

destructor TEdRawNode.Destroy;
// Destroy the node
Var
 I: integer;
begin
 // Free all subnodes
 For I := 0 to Count - 1 do begin
  FreeAndNil(fNodes[I]);
 End;
 // Free the attrib list
 FreeAndNil(Attribs);
 FreeAndNil(ResolvedParameters);
 inherited;
end;

procedure TEdRawNode.DismantleSelf;
Var
 I: Integer;
begin
 // This block will be ran just prior to deleting of the node.
 // Use this to free up any links/memory from VarMgr, FormMgr etc.
 // Must run down recursive to ensure full deletion
 For I := 0 to count - 1 do begin
  DetermineChildTypeAndCallDismantle(Self, I);
 end;
end;

function TEdRawNode.GetCount: Integer;
begin
 Result := length(fNodes);
end;

function TEdRawNode.Get_Node(Index: Integer): TEdRawNode;
begin
 Result := Nil;
 If Index >= Self.Count Then Exit;
 Result := fNodes[Index];
end;

function TedRawNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := '';
 Result := Result + Indent + '<' + Name;
 For I := 0 to Attribs.Count - 1 do begin
  Result := Result + ' ' + Attribs[I].Name + '="' + Attribs[I].Value + '"';
 end;
 Result := Result + '>' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</' + Name + '>' + #13#10;
end;

{
procedure TedRawNode.LoadFromStrings(Str: String);
var
 XML: TXMLDocument;
 Form: TForm;
 I: Integer;
begin
 // A dummy form is required otherwise the XML engine doesn't work correctly
 Form := TForm.Create(nil);
 XML := TXMLDocument.Create(Form);
 XML.XML.Text := Str;
 XML.Active := True;
 For I := 0 to XML.ChildNodes.Count - 1 do begin
  CreateChildren(XML.ChildNodes[i], self);
 End;
 XML.Active := False;
 FreeAndNil(XML);
 FreeAndNil(Form);
end;
}
procedure TEdRawNode.Resolve;
// Will resolve current node and downwards if necessary
// equal to Process in the original tree
begin
 // Raw node doesn't need to resolve any because it theoretically is an unknown
 // node, that can't be resolved/process
 ResolveChildren;
end;

procedure TedRawNode.OnParameterUpdate(Sender: TObject);
begin
 If Assigned(Parent) then Parent.OnParameterUpdate(self);
end;

procedure TEdRawNode.ResolveChildren;
// Combines all childnodes result to form one result
Var
 Node: TEdRawNode;
 I: Integer;
 Res: TResult;
 Cnt: Integer;
begin
 // If there is no children exit
 If Length(self.fNodes) = 0 then exit;
 Cnt := 0;
 For I := 0 to self.Count - 1 do begin
  Node := Self.Node[I];
  If Node.NodeType <> ntComment then begin
   Inc(Cnt);
   Node.Resolve;
   If Cnt = 1 then begin
    // 1st node clause
    Self.Result := Node.Result;
   end
    else
   begin
    // 2nd+ node clause
    if Self.Result.SensorStyle = ssNumeric then begin
     // Block required - otherwise the first numeric data will be discarded
     // because it's only stored in Result.Value not Result.Str
     { --- }
     // Set sensorstyle to NonNumeric
     Res.SensorStyle := ssNonNumeric;
     // Set the string property to a converted float value
     Res.Str := FloatToStr(Self.Result.Value);
     Self.Result := Res;
    end;
    // Convert to NonNumeric
    Res.SensorStyle := ssNonNumeric;
    // Add the result to the previous ones
    Case Node.Result.SensorStyle of
     ssNumeric: begin
                 Res.Str := Self.Result.Str + FloatToStr(Node.Result.Value);
                end;
     ssNonNumeric: Begin
                    Res.str := Self.result.str + Node.Result.Str;
                   end;
    end;
    SElf.Result := Res;
   end;
  end;
 end;
end;

Function TEdRawNode.ResolveParameters: String;
// Uses to (re)initialise all parameters
Var
 I: Integer;
begin
 Result := '';
 // Start chain reaction
 For I := 0 to Self.Count - 1 do begin
  fNodes[I].ResolveParameters;
 End;
end;

procedure TedRawNode.OnEvent(Sender: TObject);
begin
 ResolveChildren;
 If assigned(Parent) then Parent.OnEvent(self);
end;

procedure TEdRawNode.SetupSelf();
begin
end;

procedure TEdRawNode.Set_Node(Index: Integer; const Value: TEdRawNode);
begin
 If Index >= Self.Count Then Exit;
 fNodes[Index] := Value;
end;

procedure TedRawNode.LoadChildren(SelfNode: IXMLNode;
  CM2Parent: TEdRawNode);
Var
 I: Integer;
 NT: TNodeType;
 S: String;
begin
 For I := 0 to SelfNode.ChildNodes.Count - 1 do begin
  // *** TODO ***
  (* COMMENTED OUT LINES NEEDS TO BE IMPLEMENTED *)
  NT := ntUndefined;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'CML' Then NT := ntCML;
  If uppercase(SelfNode.ChildNodes[I].NodeName) = 'FORM' Then NT := ntForm;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'VISUAL' then NT := ntVisual;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'TEXT' Then NT := ntText;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'CONTENT' then NT := ntContent;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'PARAMETERS' then NT := ntParameterBlock;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'SENSOR' then NT := ntSensor;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'EVENTS' then NT := ntEvents;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'EVENTHANDLER' then NT := ntEventHandler;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'ACTION' then NT := ntAction;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'EXECUTE' then NT := ntExecute;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = 'DISPLAY' then NT := ntDisplay;
  If Uppercase(SelfNode.ChildNodes[I].NodeName) = '#TEXT' then  NT := nt_Text;
  If SelfNode.ChildNodes[I].NodeType = XMLIntf.ntComment then begin
   NT := ntComment;
  end;
//  If NT = nt_text then exit;
//  If Node.NodeType = XMLIntf.ntComment then NT := ntComment;
  If CM2Parent.NodeType = ntParameterBlock then begin
   If (not (CM2Parent.Parent.NodeType = ntCMLx)) and (not (NT = nt_text)) then NT := ntParameter;
  End;
  Case nt of
   ntAction: Begin
              (Self.Add(nt) as TEdActionNode).LoadSelf(SelfNode.ChildNodes[I]);
             end;
   ntCML: Begin
           (Self.Add(nt) as TEdCMLNode).LoadSelf(SelfNode.ChildNodes[I]);
          end;
   ntContent: Begin
               (Self.Add(nt) as TEdContentNode).LoadSelf(SelfNode.ChildNodes[I]);
              end;
   ntDisplay: Begin
               (Self.Add(nt) as TEdDisplayNode).LoadSelf(SelfNode.ChildNodes[I]);
              end;
   ntEventHandler: Begin
                    (Self.Add(nt) as TEdEventHandlerNode).LoadSelf(SelfNode.ChildNodes[I]);
                   end;
   ntEvents: Begin
              (Self.add(nt) as TedEventsNode).LoadSelf(SelfNode.ChildNodes[I]);
             end;
   ntExecute: Begin
               (self.Add(nt) as TedExecuteNode).LoadSelf(SelfNode.ChildNodes[I]);
              end;
   ntForm: Begin
            (Self.add(nt) as TEdFormNode).LoadSelf(SelfNode.ChildNodes[I]);
           end;
   ntParameterBlock: Begin
                      (Self.add(nt) as TEdParameterBlockNode).LoadSelf(SelfNode.ChildNodes[I]);
                     end;
   ntParameter: Begin
                 (self.add(nt) as TEdParameterNode).LoadSelf(SelfNode.ChildNodes[I]);
                end;
   ntSensor: Begin
              (self.add(nt) as TEdSensorNode).LoadSelf(SelfNode.ChildNodes[I]);
             End;
   ntText: Begin
            (Self.Add(nt) as TEdTextNode).LoadSelf(SelfNode.ChildNodes[I]);
           End;
   ntVisual: Begin
              (self.Add(nt) as TEdVisualNode).LoadSelf(SelfNode.ChildNodes[I]);
             end;
   ntComment: Begin
               (Self.Add(nt) as TEdCommentNode).LoadSelf(SelfNode.ChildNodes[I]);
              end; 
   nt_text: begin
             S := 'unknown';
            end;
  end;
 end;
end;

procedure TedRawNode.LoadSelf(FromNode: IXMLNode);
begin
 Name := FromNode.NodeName;
 LoadChildren(FromNode, Self);
end;

function TedRawNode.GETINDEX: Integer;
Var
 I: Integer;
begin
 Result := -1;
 If Parent = nil then exit;
 For I := 0 to Parent.Count - 1 do begin
  If Parent.Node[I] = Self then begin
   Result := I;
   break;
  end;
 end;
 If Result = -1 then begin
  MessageDlg('Location: TedRawNode.GETINDEX'+#13+#10+'Error: Node has not been found in parents node array', mtError, [mbOK], 0);
  exit;
 end;
end;

procedure TedRawNode.DeleteSelf;
Var
 Sel: Integer;
begin
 Sel := Index;
 If Sel = -1 then begin
  MessageDlg('Location: TEdRawNode.DeleteSelf'+#13+#10+'Error: Unable to delete self, Index returns "-1"', mtError, [mbOK], 0);
  exit;
 end;
 If Parent = nil then begin
  MessageDlg('Location: TEdRawNode.DeleteSelf'+#13+#10+'Error: Cannot run "DeleteSelf" when subject has no parent.', mtError, [mbOK], 0);
  exit;
 end;
 Parent.Delete(Sel);
end;

procedure TedRawNode.MoveChild(FromIndex, ToIndex: Integer);
Var
 I: Integer;
 Tempnode: TEdRawNode;
begin
 If FromIndex >= count then exit;
 If ToIndex >= Count then exit; 
 If FromIndex = ToIndex then exit;
 If FromIndex > ToIndex then begin
  // Move up
  TempNode := fNodes[FromIndex];
  For I := FromIndex downto ToIndex + 1 do begin
   fNodes[I] := fNodes[I - 1];
  end;
  fNodes[ToIndex] := TempNode;
 end
  else
 begin
  // Move Down
  TempNode := fNodes[FromIndex];
  for I := FromIndex to ToIndex - 1 do begin
   fNodes[I] := fNodes[I + 1];
  end;
  fNodes[ToIndex] := TempNode;
 end;
end;

function TedRawNode.Insert(AtIndex: Integer;
  NodeType: TNodeType): TEdRawNode;
begin
 Add(NodeType);
 MoveChild(Count - 1, AtIndex);
 Result := fNodes[AtIndex];
end;

function TedRawNode.PointerToSelf: PEdRawNode;
begin
 Result := addr(Self);
end;

procedure TedRawNode.UpdateParameter(ParamName, NewValue: string);
Var
 I: Integer;
 N: TEdRawNode;
begin
 N := nil;
 For I := 0 to count - 1 do begin
  if Node[I].NodeType = ntParameterBlock then begin
   N := Node[I];
   break;
  end;
 end;
 If N = nil then exit;
 For I := 0 to N.Count - 1 do begin
  If N[I].NodeType = ntParameter then begin
   If uppercase(N[I].Name) = uppercase(ParamName) then begin
    (N[I] as TedParameterNode).Value := NewValue;
    (N[I] as TEdParameterNode).OnParameterUpdate(N[I]);
   end;
  end;
 end;
end;

{ TAttribList }

procedure TAttribList.Add(Name, Value: String);
begin
 SetLength(fList, Length(fList) + 1);
 fList[Length(fList) - 1].Name := Name;
 fList[Length(fList) - 1].Value := Value;
end;

procedure TAttribList.Clear;
begin
 SetLength(fList, 0);
end;

procedure TAttribList.Delete(Index: Integer);
Var
 I : Integer;
begin
 If Index >= Length(fList) Then exit;
 For I := Index to Length(fList) - 2 do begin
  fList[I] := fList[I + 1];
 End;
 setLength(fList, Length(fList) - 1);
end;

function TAttribList.GetAttrib(Index: Integer): TAttrib;
begin
 Result.Name := '';
 Result.Value := '';
 If Index >= Length(fList) Then exit;
 result := fList[Index];
end;

function TAttribList.GetCount: Integer;
begin
 Result := Length(fList);
end;

procedure TAttribList.SetAttrib(Index: Integer; const Value: TAttrib);
begin
 If Index >= Length(fList) Then exit;
 fList[Index] := Value;
end;

{ TEdFormNode }

function TEdFormNode.AddVisualNode(GUID: TGUID; X, Y: Integer): TEdVisualNode;
begin
 Result := (GetContentNode.Add(ntVisual) as TEdVisualNode);
 Result.SetupSelf(GUID, X, Y);
 oeMain.SelectNode(Result);
end;

constructor TEdFormNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntForm;
end;

procedure TEdFormNode.DismantleSelf;
begin
  inherited;
  Owner.EdFormMgr.Delete(Self);
end;

function TEdFormNode.GetContentNode: TEdContentNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If fNodes[I].NodeType = ntContent then begin
   Result := (fNodes[I] as TEdContentNode);
   exit;
  end;
 end;
end;

function TEdFormNode.GetEventsNode(CanCreate: Boolean): TEdEventsNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If Node[I].NodeType = ntEvents then begin
   Result := (Node[I] as TEdEventsNode);
   exit;
  end;
 end;
 If not Cancreate then exit;
 Result := (Add(ntEvents) as TEdEventsNode);
 Result.SetupSelf;
end;

function TEdFormNode.GetParameterBlockNode: TEdParameterBlockNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If fNodes[I].NodeType = ntParameterBlock then begin
   Result := (fNodes[I] as TEdParameterBlockNode);
   exit;
  end;
 end;
end;

procedure TEdFormNode.LoadSelf(FromNode: IXMLNode);
Var
 I: Integer;
 N: String;
begin
 For I := 0 to FromNode.AttributeNodes.Count -1 do begin
  N := FromNode.AttributeNodes[I].NodeName;
  If uppercase(N) = 'NAME' then begin
   if FromNode.AttributeNodes[I].NodeValue <> null then begin
    fFormName := FromNode.AttributeNodes[I].NodeValue;
   end;
  end;
 end;
 Form := Owner.EdFormMgr.Add(Owner.ParentForm, self);
 LoadChildren(FromNode, Self);
end;

procedure TEdFormNode.OnParameterUpdate(Sender: TObject);
begin
 ReloadParameters;
end;

procedure TEdFormNode.ReloadParameters;
Var
 PBNode: TEdParameterBlockNode;
 I: Integer;
 S, N, V: String;
begin
 PBNode := GetParameterBlockNode;
 S := PBNode.ResolveParameters;
 For I := 1 to Countfilt(S, '|') do begin
  N := GetFilt(S, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  SetParameter(N, V);
 end;
end;

procedure TEdFormNode.SetParameter(Parameter, Value: String);
Var
 I: integer;
 SnapTo: Boolean;
begin
 If Options.Visual.SnapToGrid then begin
  SnapTo := true;
  Options.Visual.SnapToGrid := false;
 end
  else
 begin
  SnapTo := false;
 end;
 Parameter := Uppercase(Parameter);
 If Parameter = 'ALPHA' then begin
  Form.Alpha := StrToInt(Value);
 end;
 If Parameter = 'COLOR' then begin
  Form.Color := RGBToColor(Value);
 end;
 If Parameter = 'LEFT' then begin
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Left := I + oeMain.DesktopImage1.Left;
             end;
   ctiModified: begin
                 Form.Left := I + oeMain.DesktopImage1.Left;
                 GetParameterBlockNode.ChangeParameterValue('left', IntTostr(Form.Left));
                end;
   ctiError: begin
              Form.Left := 0;
              GetParameterBlockNode.ChangeParameterValue('left', '0');
             end;
  end;
 end;
 If Parameter = 'RIGHT' then begin
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Left := (oeMain.DesktopImage1.Width - (Form.Width + I)) + oeMain.DesktopImage1.Left;
             end;
   ctiModified: begin
                 Form.Left := (oeMain.DesktopImage1.Width - (Form.Width + I)) + oeMain.DesktopImage1.Left;
                 GetParameterBlockNode.ChangeParameterValue('right', IntToStr(I));
                end;
   ctiError: begin
              Form.Left := 0;
              GetParameterBlockNode.ChangeParameterValue('left', IntTostr(oeMain.DesktopImage1.Width - Form.Width));
             end;
  end;
 end;
 If Parameter = 'BOTTOM' then begin
  Form.Top := oeMain.DesktopImage1.Height - (Form.Height + StrToInt(Value)) + oeMain.DesktopImage1.Top;
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Top := (oeMain.DesktopImage1.Width - (Form.Width + I)) + oeMain.DesktopImage1.Top;
             end;
   ctiModified: begin
                 Form.Top := (oeMain.DesktopImage1.Width - (Form.Width + I)) + oeMain.DesktopImage1.Top;
                 GetParameterBlockNode.ChangeParameterValue('bottom', IntToStr(I));
                end;
   ctiError: begin
              Form.Top := 0;
              GetParameterBlockNode.ChangeParameterValue('top', IntTostr(oeMain.DesktopImage1.Height - Form.Height));
             end;
  end;
 end;
 If Parameter = 'HEIGHT' then begin
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Height := I;
             end;
   ctiModified: begin
                 Form.Height := I;
                 GetParameterBlockNode.ChangeParameterValue('height', IntToStr(I));
                end;
   ctiError: begin
              Form.Height := 100;
              GetParameterBlockNode.ChangeParameterValue('height', '100');
             end;
  end;
 end;
 If Parameter = 'SHOW' then begin
  If uppercase(Value) = 'YES' Then Form.Visible := true
   else Form.Visible := False;
 end;
 If Parameter = 'TOP' then begin
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Top := I + oeMain.DesktopImage1.Top;
             end;
   ctiModified: begin
                 Form.Top := I + oeMain.DesktopImage1.Top;
                 GetParameterBlockNode.ChangeParameterValue('top', IntToStr(I));
                end;
   ctiError: begin
              Form.top := 0;
              GetParameterBlockNode.ChangeParameterValue('top', '0');
             end;
  end;
 end;
 If Parameter = 'TRANSPARENT' then begin
  If uppercase(Value) = 'YES' then Form.Transparent := true
   else Form.Transparent := False;
 end;
 If Parameter = 'TRANSPARENTCOLOR' then begin
  Form.TransparentColor := RGBToColor(Value);
 end;
 If Parameter = 'WIDTH' then begin
  case ConvertToInteger(Value, I) of
   ctiValid: begin
              Form.Width := I;
             end;
   ctiModified: begin
                 Form.Width := I;
                 GetParameterBlockNode.ChangeParameterValue('width', IntToStr(I));
                end;
   ctiError: begin
              Form.Height := 200;
              GetParameterBlockNode.ChangeParameterValue('width', '200');
             end;
  end;
 end;
 If Parameter = 'NAME' then begin
  fFormName := Value;
 end;
 // WindowMode
 Form.Update;
 If SnapTo then begin
  Options.Visual.SnapToGrid := true;
 end;
end;

procedure TEdFormNode.SetupSelf(X, Y: Integer);
Var
 ParamNode: TEdParameterBlockNode;
 PNode: TEdParameterNode;
begin
 // *** OPTION LINK IN *** - Default values
 // Create a form and store index so we can link to it again later
 Form := Owner.EdFormMgr.Add(Owner.ParentForm, self);
 // Create the parameters node
 Add(ntParameterBlock);
 // create the Contents node
 Add(ntContent); // as TedContentNode).SetupSelf;
 ParamNode := GetParameterBlockNode;
 // Add Alpha
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'alpha';
 PNode.OIType := oitRanged;
 PNode.OITString := '1|255';
 PNode.Value := oeMain.FESL.GetValue('Alpha');
 // Add Color
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'color';
 PNode.OIType := oitColor;
 PNode.OITString := '';
 PNode.Value := oeMain.FESL.GetValue('Color');
 // Add Left
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'left';
 PNode.OIType := oitInteger;
 PNode.OITString := '';
 If X = -1 then PNode.Value := '200'
  else PNode.Value := IntTostr(X);
 // Add Height
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'height';
 PNode.OIType := oitInteger;
 PNode.OITString := '';
 PNode.Value := '200';
 // Add Show
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'show';
 PNode.OIType := oitFixed;
 PNode.OITString := 'Yes|No';
 PNode.Value := oeMain.FESL.GetValue('Show');
 // Add Top
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'top';
 PNode.OIType := oitInteger;
 PNode.OITString := '';
 If Y = -1 then PNode.Value := '200'
  else PNode.Value := IntToStr(Y);
 // Add Transparent
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'transparent';
 PNode.OIType := oitFixed;
 PNode.OITString := 'Yes|No';
 PNode.Value := oeMain.FESL.GetValue('Transparent');
 // Add TransparentColor
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'transparentcolor';
 PNode.OIType := oitColor;
 PNode.OITString := '';
 PNode.Value := oeMain.FESL.GetValue('TransparentColor');
 // Add Width
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'width';
 PNode.OIType := oitInteger;
 PNode.OITString := '';
 PNode.Value := '300';
 // Add WindowMode
 PNode := (ParamNode.Add(ntParameter) as TEdParameterNode);
 PNode.Name := 'windowmode';
 PNode.OIType := oitFixed;
 PNode.OITString := 'Normal|PinToDesktop|AlwaysOnTop';
 PNode.Value := oeMain.FESL.GetValue('WindowMode');
 ReloadParameters;
end;

function TEdFormNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): string;
Var
 I: Integer;
begin
 Result := Indent + '<form name="' + FormName + '">' + #13#10;
 For I := 0 to Count - 1 do begin
  If ObjectOnly then begin
   If fNodes[I].NodeType = ntContent then begin
    Result := Result + Indent + Options.Files.CMLIndent + '<Content>' + #13#10 +
                       Indent + Options.Files.CMLIndent + '</Content>' + #13#10;
   end
    else
   begin
    Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
   end;
  end
   else
  begin
   Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
  end;
 end;
 Result := Result + Indent + '</form>' + #13#10;
End;

{ TEdVisualNode }

function TEdVisualNode.AddSensorNode(PlugInName, SensorName, UpdateStr: String;
  Dedicated: Boolean): TEdSensorNode;
begin
 Result := (GetContentNode.Add(ntSensor) as TEdSensorNode);
 Result.SetupSelf(PlugInName, SensorName, UpdateStr, Dedicated, '');
 oeMain.SelectNode(Result);
end;

constructor TEdVisualNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntVisual;
end;

procedure TEdVisualNode.DismantleSelf;
Var
 aNode: TEdRawNode;
begin
 aNode := Self;
 While (aNode.NodeType <> ntForm) do begin
  aNode := aNode.Parent;
  If aNode = nil then begin
   MessageDlg('Location: TEdVisualNode.DismantleSelf'+#13+#10+'Error: Unable to find parent form.', mtError, [mbOK], 0);
   exit;
  end;
 end;
 (aNode as TEdFormNode).Form.VisualPanelList.Delete(self);
 inherited;
end;

function TEdVisualNode.GetContentNode: TEdContentNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If fNodes[I].NodeType = ntContent then begin
   Result := (fNodes[I] as TEdContentNode);
   exit;
  end;
 end;
end;

function TEdVisualNode.GetEventsNode(CanCreate: Boolean): TEdEventsNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If Node[I].NodeType = ntEvents then begin
   Result := (Node[I] as TEdEventsNode);
   exit;
  end;
 end;
 If not Cancreate then exit;
 Result := (Add(ntEvents) as TEdEventsNode);
 Result.SetupSelf;
end;

function TEdVisualNode.GetParameterBlockNode: TEdParameterBlockNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If Node[I].NodeType = ntParameterBlock then Result := (Node[I] as TEdParameterBlockNode);
 end;
end;

procedure TEdVisualNode.LoadSelf(FromNode: IXMLNode);
Var
 aNode: TEdRawNode;
 I: Integer;
 OK: Boolean;
 GUID: TGUID;
begin
 For I := 0 to FromNode.AttributeNodes.Count - 1 do begin
  If uppercase(FromNode.AttributeNodes[I].NodeName) = 'NAME' then begin
   VisualName := FromNode.AttributeNodes[I].NodeValue;
  end;
 end;
 aNode := self;
 OK := false;
 repeat
  aNode := aNode.Parent;
  If (aNode.NodeType = ntForm) or (aNode = nil) then ok := true;
 until OK;
 If aNode = nil then begin
  raise Exception.Create('Theoretical Error - Visual node has no parent form' + #13#10 + 'Location: TEdVisualNode.SetupSelf');
 end;
 VisualID := '';
 oeMain.vpl.GetGUID(VisualName, GUID);
 VisualGUID := GUID;
 LastFormNode := (aNode as TEdFormNode);
 /// Add Visual panel to list on formpanel
 Visual := LastFormNode.Form.VisualPanelList.Add(VisualGUID, self, 0, 0, false);
 LoadChildren(FromNode, Self);
end;

procedure TEdVisualNode.OnEvent(Sender: TObject);
begin
 ResolveChildren;
end;

procedure TEdVisualNode.OnParameterUpdate(Sender: TObject);
begin
 Visual.UpdateParameters;
end;

procedure TEdVisualNode.ResolveChildren;
Var
 Node: TEdRawNode;
begin
 Node := GetContentNode;
 If Node = nil then begin
  // Since there is no content node, there is nothing to resolve so exit
  exit;
 end;
 Node.Resolve;
 Result := Node.Result;
 Visual.Result := Result;
 Visual.Paint;
end;

procedure TEdVisualNode.SetupSelf(GUID: TGUID; X, Y: Integer);
Var
 aNode: TEdRawNode;
 OK: Boolean;
 TheName: String;
begin
 // First find the parent form, by looking back up the tree
 aNode := self;
 OK := false;
 repeat
  aNode := aNode.Parent;
  If (aNode.NodeType = ntForm) or (aNode = nil) then ok := true;
 until OK;
 If aNode = nil then begin
  raise Exception.Create('Theoretical Error - Visual node has no parent form' + #13#10 + 'Location: TEdVisualNode.SetupSelf');
 end;
 VisualID := '';
 VisualGUID := GUID;
 LastFormNode := (aNode as TEdFormNode);
 /// Add Visual panel to list on formpanel
 TheName := '';
 If not oeMain.vpl.GetName(VisualGUID, TheName) then begin
  Raise Exception.Create('Visual plugin list is unable to provide a name for this GUID' + #13#10 + GUIDToString(VisualGUID));
 end
  else
 begin
  VisualName := TheName;
 end;
 Visual := LastFormNode.Form.VisualPanelList.Add(VisualGUID, self, X, Y);
 If oeMain.TutorialList.WaitingForCallback <> '' then begin
  oeMain.TutorialList.Callback(self, VisualName);
 end;
end;

function TEdVisualNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := Indent + '<visual name="' + VisualName + '"';
 If VisualID <> '' then Result := Result + ' VisualID="' + VisualID + '"';
 Result := Result + '>' + #13#10;
 For I := 0 to Count - 1 do begin
  If ObjectOnly then begin
   If fNodes[I].NodeType = ntContent then begin
    Result := Result + Indent + Options.Files.CMLIndent + '<Content>' + #13#10 +
                       Indent + Options.Files.CMLIndent + '</Content>' + #13#10;
   end
    else
   begin
    Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
   end;
  end
   else
  begin
   Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
  end;
 end;
 Result := Result + Indent + '</visual>' + #13#10;
End;

{ TEdCMLNode }

constructor TEdCMLNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntCML;
end;

procedure TEdCMLNode.LoadSelf(FromNode: IXMLNode);
Var
 I: Integer;
begin
 For I := 0 to FromNode.AttributeNodes.count - 1 do begin
  if uppercase(fromnode.attributeNodes[I].NodeName) = 'VERSION' then begin
   fCMLVersion := fromnode.AttributeNodes[I].NodeValue;
  end;
 end;
 LoadChildren(FromNode, self);
end;

procedure TEdCMLNode.SetupSelf(Version: String);
begin
 fCMLVersion := Version;
end;

function TEdCMLNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := Indent + '<cml version="' + CMLVersion + '">' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</cml>' + #13#10;
end;

{ TEdParameterBlockNode }

procedure TEdParameterBlockNode.AddParameterNode(PName, PValue: String);
Var
 ParamNode: TEdParameterNode;
begin
 ParamNode := (Add(ntParameter) as TEdParameterNode);
 ParamNode.Name := PName;
 ParamNode.Value := PValue;
end;

procedure TEdParameterBlockNode.ChangeParameterValue(ParameterName,
  ParameterValue: string);
Var
 I: Integer;
begin
 For I := 0 to count - 1 do begin
  If uppercase(Node[I].Name) = uppercase(ParameterName) then begin
   (Node[I] as TEdParameterNode).Value := ParameterValue;
   exit;
  end;
 end;
end;

constructor TEdParameterBlockNode.Create(Owner: TEdCM2Tree;
  Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntParameterBlock;
 Name := 'parameters';
end;

function TEdParameterBlockNode.GetParameterNode(
  ParameterName: String): TEdParameterNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to Count - 1 do begin
  If Node[I].NodeType = ntParameter then begin
   If uppercase((Node[I] as TEdParameterNode).Name) = uppercase(ParameterName) then begin
    Result := (Node[I] as TEdParameterNode);
    Exit;
   end;
  end;
 end;
end;

procedure TEdParameterBlockNode.LoadSelf(FromNode: IXMLNode);
begin
 LoadChildren(fromnode, self);
 OnParameterUpdate(self);
end;

procedure TEdParameterBlockNode.OnEvent(Sender: TObject);
begin
 If Parent = nil then exit;
 Parent.OnParameterUpdate(self);
end;

function TEdParameterBlockNode.ResolveParameters: String;
Var
 I: Integer;
begin
 Result := '';
 For I := 0 to count - 1 do begin
  If Node[I].NodeType = ntParameter then begin
   Result := Result + '|' + (Node[I] as TEdParameterNode).ResolveParameters;
  end;
 end;
 Result := Copy(Result, 2, length(Result));
end;

procedure TEdParameterBlockNode.SetupSelf;
begin
 Name := 'parameters';
end;

function TEdParameterBlockNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 If count = 0 then begin
  Result := '';
  exit;
 end;
 Result := '';
 Result := Result + Indent + '<parameters>' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</parameters>' + #13#10;
end;

{ TedContentNode }

constructor TedContentNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntContent;
 name := 'content';
end;

procedure TedContentNode.SetupSelf;
begin
 name := 'content';
end;

{ TedDisplayNode }

function TedDisplayNode.AddFormNode(X, Y: Integer): TEdFormNode;
begin
 Result := (Add(ntForm) as TEdFormNode);
 Result.SetupSelf(X, Y);
end;

constructor TedDisplayNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntDisplay;
 Name := 'display';
end;

procedure TedDisplayNode.SetupSelf;
begin
 name := 'display';
end;

{ TEdParameterNode }

constructor TEdParameterNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntParameter;
 OIType := oitUnknown;
 OITString := '';
 Name := '';
end;

procedure TEdParameterNode.LoadSelf(FromNode: IXMLNode);
begin
 fName := FromNode.NodeName;
 If FromNode.ChildNodes.count = 1 then begin
  If uppercase(FromNode.ChildNodes[0].NodeName) = '#TEXT' then begin
   fValue := FromNode.ChildNodes[0].NodeValue;
  end;
 end
  else
 begin
  // This should work if it contains a sensor node
  LoadChildren(FromNode, Self);
 end;
end;

function TEdParameterNode.ResolveParameters: String;
begin
 Result := '';
 If Count > 0 then begin
  Result := fName;
  Case Node[0].Result.SensorStyle of
   ssNonNumeric: Result := Result + '=' + Node[0].Result.Str;
   ssNumeric: Result := Result + '=' + FloatToStr(Node[0].Result.Value);
  end;
 end
  else
 begin
  Result := Name + '=' + Value;
 end;
end;

function TEdParameterNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 If (Count = 0) and (Value = '') then begin
  Result := '';
  exit;
 end;
 Result := Indent + '<' + Name + '>';
 If Count > 0 then begin
  Result := Result + #13#10;
  For I := 0 to Count - 1 do begin
   Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
  end;
  Result := Result + Indent + '</' + Name + '>' + #13#10;
 end
  else
 begin
  Result := Result + Value + '</' + Name + '>' + #13#10;
 end;
end;

{ TEdTextNode }

constructor TEdTextNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntText;
end;

procedure TEdTextNode.LoadSelf(FromNode: IXMLNode);
begin
 If FromNode.ChildNodes.count = 1 then begin
  If uppercase(FromNode.ChildNodes[0].NodeName) = '#TEXT' then begin
   fTextValue := FromNode.ChildNodes[0].NodeValue;
  end;
 end;
 Parent.OnEvent(self);
end;

procedure TEdTextNode.Resolve;
begin
 Result.SensorStyle := ssNonNumeric;
 Result.Str := fTextValue;
end;

procedure TEdTextNode.SetupSelf(Text: String);
begin
 fTextValue := Text;
end;

function TEdTextNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
begin
 Result := Indent + '<text>' + TextValue + '</text>' + #13#10;
end;

{ TEdSensorNode }

constructor TEdSensorNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntSensor;
 fDedicated := False;
 fUpdate := '1';
end;

procedure TEdSensorNode.DismantleSelf;
begin
 Owner.GetVarMgr^.Delete(self);
 VariableWin.OnEvent(Self, true);
 inherited;
end;

function TEdSensorNode.GetParameterBlockNode: TEdParameterBlockNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to count - 1 do begin
  If fNodes[I].NodeType = ntParameterBlock then begin
   Result := (fNodes[I] as TEdParameterBlockNode);
   exit;
  end;
 end;
End;

procedure TEdSensorNode.LoadSelf(FromNode: IXMLNode);
Var
 I, II: Integer;
 N, V: String;
 Linked: Boolean;
begin
 Linked := false;
 For I := 0 to FromNode.AttributeNodes.Count -1 do begin
  N := Uppercase(FromNode.AttributeNodes[I].NodeName);
  If FromNode.AttributeNodes[I].NodeValue <> null then begin
   V := FromNode.AttributeNodes[I].NodeValue;
   If N = 'NAME' then begin
    If V[1] = '@' then begin
     Linked := true;
     V := Copy(V, 2, length(V));
    end
     else
    begin
     fPlugInName := GetFilt(V, 1, '.');
     fSensorName := GetFilt(V, 2, '.');
    end;
   end;
   If N = 'UPDATE' then begin
    fUpdate := V;
   end;
   If N = 'DEDICATED' then begin
    If uppercase(V) = 'YES' then fDedicated := true
     else fDedicated := false;
   end;
   If N = 'ID' then begin
    fID := V;
   end;
  end;
 end;

 If not linked then begin
  For I := 0 to oeMain.gpl.Count - 1 do begin
   If uppercase(oeMain.gpl[I].Name) = uppercase(pluginName) Then begin
    PlugInGUID := oeMain.gpl[I].GUID;
    For II := 0 to oeMain.gpl[I].SensorList.Count - 1 do begin
     If uppercase(oeMain.gpl[I].SensorList[II].Name) = uppercase(SensorName) then begin
      SensorIndex := II;
      break;
     end;
    end;
    break;
   end;
  end;
  // Set Safemodename
  Inc(SensorObjCount);
  fSafeModeName := 'SENSOR#' + IntToStr(SensorObjCount) + '|' + PlugInName + '.' + SensorName;
  // Update pointer
  UpdateGenPlugIn;
  // Create item in VarMgr
  VarItem := Owner.VarMgr^.AddGenP(Self, SafeModeName, fUpdate, GenPlugIn, SensorIndex, '', fDedicated, fID);
  VarItem.AddEvent(Self.OnEvent);
 end
  else
 begin
  VarItem := Owner.VarMgr^.AddViaID(V, fSafeModeName, Self);
  VarItem.AddEvent(Self.OnEvent);
  LinkedTo := (VarItem.Nodes[0] as TEdSensorNode);
  Add(ntParameterBlock);
 end;

 // Launch sensor probe in the variable manage
 // The empty strings are parameters, they will be added automaticlly by running "UpdateParameters"
 LoadChildren(FromNode, Self);
end;

procedure TEdSensorNode.OnEvent(Sender: TObject);
begin
 Resolve;
 DetermineParentTypeAndCallEvent(Self);
 VariableWin.OnEvent(Self);
end;

procedure TEdSensorNode.OnParameterUpdate(Sender: TObject);
begin
 UpdateParameters;
end;

procedure TEdSensorNode.RebuildVarItem;
Var
 List: Array of TNotifyEvent;
 I: Integer;
begin
 UpdateVarItem;
 SetLength(List, VarItem^.EventList.Count);
 For I := 0 to VarItem^.EventList.Count - 1 do begin
  List[I] := VarItem^.EventList[I];
 end;
 Owner.GetVarMgr^.Delete(self);
 VarItem := Owner.VarMgr^.AddGenP(self, SafeModeName, UpdateStr, GenPlugIn, SensorIndex, '', fDedicated, IDName);
 For I := 0 to length(List) - 1 do begin
  VarItem^.AddEvent(List[I]);
 end;
end;

procedure TEdSensorNode.Resolve;
begin
 UpdateVarItem;
 Result := VarItem^.Result;
end;

function TEdSensorNode.ResolveParameters: String;
begin
 If GetParameterBlockNode = nil then begin
  Result := '';
  exit;
 end;
 Result := GetParameterBlockNode.ResolveParameters;
end;

procedure TEdSensorNode.SetupSelf(PlugName, PlugSensor: String; Update: String; Dedicated: Boolean; ID: String);
Var
 PI, SI, I, II: Integer;
 ParamNode: TEdParameterBlockNode;
begin
 ParamNode := (Add(ntParameterBlock) as TEdParameterBlockNode);
 PlugInName := PlugName;
 SensorName := PlugSensor;
 fUpdate := Update;
 fDedicated := Dedicated;
 fID := ID;
 For I := 0 to oeMain.gpl.Count - 1 do begin
  If uppercase(oeMain.gpl[I].Name) = uppercase(plugName) Then begin
   PlugInGUID := oeMain.gpl[I].GUID;
   For II := 0 to oeMain.gpl[I].SensorList.Count - 1 do begin
    If uppercase(oeMain.gpl[I].SensorList[II].Name) = uppercase(PlugSensor) then begin
     SensorIndex := II;
     break;
    end;
   end;
   break;
  end;
 end;
 // Set Safemodename
 Inc(SensorObjCount);
 fSafeModeName := 'SENSOR#' + IntToStr(SensorObjCount) + '|' + PlugName + '.' + PlugSensor;
 // Update pointer
 UpdateGenPlugIn;
 // Load parameters (Defaults)
 oeMain.GESL.GetIndexByName(PlugName + '.' + PlugSensor, PI, SI);
 For I := 0 to oeMain.GESL[PI][SI].Count - 1 do begin
  ParamNode.AddParameterNode(oeMain.GESL[PI][SI][I].Name, oeMain.GESL[PI][SI][I].Value);
 end;
 // Launch sensor probe in the variable manage
 // The empty strings are parameters, they will be added automaticlly by running "UpdateParameters"

 VarItem := Owner.VarMgr^.AddGenP(self, SafeModeName, Update, GenPlugIn, SensorIndex, '', Dedicated, ID);
 VarItem.AddEvent(Self.OnEvent);
 UpdateParameters;
end;

procedure TEdSensorNode.SetupSelfFromLinked(ID: String);
begin
  VarItem := Owner.VarMgr^.AddViaID(ID, fSafeModeName, Self);
  VarItem.AddEvent(Self.OnEvent);
  LinkedTo := (VarItem.Nodes[0] as TEdSensorNode);
  Add(ntParameterBlock);
end;

procedure TEdSensorNode.UpdateGenPlugIn;
begin
 GenPlugIn := oeMain.gpl.GetPlugInAddr(PlugInGUID);
end;

procedure TEdSensorNode.UpdateParameters;
Var
 ParamString: String;
 ReInit: Boolean;
 UPD: Integer;
begin
 ReInit := False;
 If GetParameterBlockNode = nil then ParamString := ''
  else ParamString := GetParameterBlockNode.ResolveParameters;
 VariableWin.OnParameter(Self, ParamString);
 UpdateVarItem;
 VarItem^.Parameters := ParamString;
 VarItem^.ID := IDName;
 If (VarItem^ is TGenItem) then begin
  If (VarItem^ as TGenItem).RunningDedicated <> fDedicated then ReInit := true;
  Try
   UPD := StrToInt(fUpdate);
  except
   on econverterror do begin
    upd := -1;
   end;
  end;
  If upd = -1 then ReInit := True;
  If not ReInit Then begin
   // check for change in the updateStr line, change direct
   If (VarItem^ as TGenItem).UpdateFreq <> Upd then begin
    (VarItem^ as TGenItem).UpdateFreq := Upd;
    ReInit := True; // here it's used as an indicator that VarVariable window should update
   end;
  end;
 end;
 If (VarItem^ Is TAdvTimedGenItem) then begin
  Try
   UPD := StrToInt(fUpdate);
  except
   on EConvertError do begin
    UPD := -1;
   end;
  end;
  If Upd > -1 then Reinit := true;
  If not ReInit then begin
   If ((VarItem^ as TAdvTimedGenItem).UpdateString <> fUpdate) then begin
    (VarItem^ as TAdvTimedGenItem).UpdateString := fUpdate;
    ReInit := true; // here it's used as an indicator that VarVariable window should update
   end;
  end;
 end;
 If ReInit then begin
  RebuildVarItem;
  VarItem^.Parameters := ParamString;
  If VariableWin.Visible then begin
   VariableWin.BuildLV;
  end;
 end;
 // Since the parameters have changed, it would be a good idea to repoll with the new parameters
 VarItem^.Poll(Self);
 // There shouldn't be a need to send send the event upwards, since where polling the sensor is will migrate though OnEvent
end;

procedure TEdSensorNode.UpdateVarItem;
begin
 VarItem := Owner.VarMgr^.GetP(SafeModeName);
end;

function TEdSensorNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
 S: String;
 VI: TVarItem;
 WriteAsLinked: boolean;
begin
 WriteAsLinked := False;
 VI := Owner.VarMgr^.GetP(SafeModeName)^;
 If VI.Nodes.Count > 1 then begin
  If VI.SaveCount > 0 then begin
   WriteAsLinked := true;
  end;
 end;
 If not writeaslinked then begin
  Result := '';
  If IsDedicated then S := 'yes' // Stored in a local S, instead of performed on-fly
   else S := 'no';             // which would repeat the possibly quite large Result string
  Result := Result + Indent + '<sensor name="' + PlugInName + '.' + SensorName + '" update="' + UpdateStr + '" dedicated="' + S + '"';
  If IDName <> '' then Result := Result + ' id="' + IDName + '">' + #13#10
   else Result := Result + '>' + #13#10;
  For I := 0 to Count - 1 do begin
   Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
  end;
  Result := Result + Indent + '</sensor>' + #13#10;
 end
  else
 begin
  // Write Linked sensor
  Result := '';
  Result := Result + Indent + '<sensor name="@' + VI.ID + '"/>' + #13#10;
 end;
 Inc(Owner.VarMgr^.GetP(SafeModeName).SaveCount);
end;

{ TEdEventsNode }

constructor TEdEventsNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fNodeType := ntEvents;
 Name := 'events';
end;

procedure TEdEventsNode.DeleteEvent(Trigger, Mods, Command: String);
Var
 I, II, III: Integer;
 DI, DII, DIII: Boolean;
begin
 // ALL FOR LOOPS MUST BE ALTERED SINCE YOU ACTIVELY DELETE THEM WHILE THEY ARE RUNNING THROUGH THEM
 I := 0;
 While I < Count do begin
  DI := false;
  If fNodes[I].NodeType = ntEventHandler then begin
   If uppercase((fNodes[I] as TEdEventHandlerNode).Trigger) = uppercasE(trigger) then begin
    II := 0;

    While II < fNodes[I].Count do begin
    // For II := 0 to fNodes[I].Count - 1 do begin
     DII := False;
     If fNodes[I][II].NodeType = ntAction then begin
      If uppercase((fNodes[I][II] as TEdActionNode).ModKeys) = uppercase(Mods) then begin
       III := 0;

       While III < fNodes[I][II].Count do begin
       //For III := 0 to fNodes[I][II].Count - 1 do begin
        DIII := false;
        If fNodes[I][II][III].NodeType = ntExecute then begin
         If uppercase((fNodes[I][II][III] as TEdExecuteNode).Execute) = uppercase(Command) then begin
          fNodes[I][II][III].DeleteSelf;
          DIII := true;
         end;
        end;
        If Not DIII then Inc(III);
       end;

       // If action node is empty remove it
       If fNodes[I][II].Count = 0 then begin
        fNodes[I][II].DeleteSelf;
        DII := true;
       end;
      end;
     end;
     If not DII then inc(II);
    end;

    // If trigger node is empty remove it
    If fNodes[I].Count = 0 then begin
     fNodes[I].DeleteSelf;
     DI := true;
    end;
   end;
  end;
  If not DI then Inc(I);
 end;
 // If events node is empty remove it
 If Count = 0 then begin
  DeleteSelf;
 end;
end;

function TEdEventsNode.GetEventHandlerNode(Trigger: String;
  CanCreate: Boolean): TEdEventHandlerNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to Count - 1 do begin
  If Node[I].NodeType = ntEventHandler then begin
   If uppercase((Node[I] as TEdEventHandlerNode).Trigger) = Uppercase(Trigger) then begin
    Result := (Node[I] as TEdEventHandlerNode);
    exit;
   end;
  end;
 end;
 If not Cancreate then exit;
 Result := (Add(ntEventHandler) as TEdEventHandlerNode);
 Result.SetupSelf(Trigger);
end;

function TEdEventsNode.GetEvents: String;
Var
 I, II, III: Integer;
begin
 Result := '';
 For I := 0 to Count - 1 do begin
  If fNodes[I].NodeType = ntEventHandler then begin
   For II := 0 to fNodes[I].Count - 1 do begin
    If fNodes[I][II].NodeType = ntAction then begin
     For III := 0 to fNodes[I][II].Count - 1 do begin
      If fNodes[I][II][III].NodeType = ntExecute then begin
       Result := Result +
        (fNodes[I] as TEdEventHandlerNode).Trigger + '>' +
        (fNodes[I][II] as TEdActionNode).ModKeys + '>' +
        (fNodes[I][II][III] as TEdExecuteNode).Execute + '>|';
      end;
     end;
    end;
   end;
  end;
 end;
 If Result[Length(result)] = '|' then begin
  Result := copy(Result, 1, length(result)-1);
 end;
end;

function TEdEventsNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := Indent + '<events>' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</events>' + #13#10;
End;

{ TEdEventHandlerNode }

constructor TEdEventHandlerNode.Create(Owner: TEdCM2Tree;
  Parent: TEdRawNode);
begin
 Inherited;
 fNodeType := ntEventHandler;
 Name := 'eventhandler';
end;

function TEdEventHandlerNode.GetActionNode(Mods: String;
  CanCreate: Boolean): TEdActionNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to Count - 1 do begin
  If (fNodes[I].NodeType = ntAction) then begin
   if uppercase((fNodes[I] as TEdActionNode).ModKeys) = uppercase(Mods) then begin
    Result := (fNodes[I] as TEdActionNode);
    Exit;
   end;
  end;
 end;
 If not cancreate then exit;
 Result := (Add(ntAction) as TEdActionNode);
 Result.SetupSelf(Mods);
end;

procedure TEdEventHandlerNode.LoadSelf(FromNode: IXMLNode);
Var
 I: Integer;
begin
 For I := 0 to FromNode.AttributeNodes.Count - 1 do begin
  If uppercase(FromNode.AttributeNodes[I].NodeName) = 'TRIGGER' then begin
   fTrigger := FromNode.AttributeNodes[I].NodeValue;
  end;
 end;
 LoadChildren(FromNode, Self);
end;

procedure TEdEventHandlerNode.SetupSelf(TriggerStr: String);
begin
 fTrigger := TriggerStr;
end;

function TEdEventHandlerNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := Indent + '<eventhandler trigger="' + fTrigger + '">' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</eventhandler>' + #13#10;
End;

{ TEdActionNode }

constructor TEdActionNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 Inherited;
 fnodetype := ntAction;
 name := 'Action';
end;

function TEdActionNode.GetExecuteNode(Execute: String;
  CanCreate: Boolean): TEdExecuteNode;
Var
 I: Integer;
begin
 Result := nil;
 For I := 0 to Count - 1 do begin
  If (fNodes[I].NodeType = ntExecute) then begin
   If uppercase((fNodes[I] as TEdExecuteNode).Execute) = uppercase(Execute) then begin
    Result := (fNodes[I] as TEdExecuteNode);
    Exit;
   end;
  end;
 end;
 If Not Cancreate then exit;
 Result := (add(ntExecute) as TEdExecuteNode);
 Result.SetupSelf(Execute);
end;

procedure TEdActionNode.LoadSelf(FromNode: IXMLNode);
Var
 I: Integer;
begin
 For I := 0 to FromNode.AttributeNodes.Count - 1 do begin
  If uppercase(FromNode.AttributeNodes[I].NodeName) = 'MODIFIER' then begin
   fModKeys := FromNode.AttributeNodes[I].NodeValue;
  end;
 end;
 LoadChildren(FromNode, Self);
end;

procedure TEdActionNode.SetupSelf(ModKeyStr: String);
begin
 fModKeys := ModKeyStr;
end;

function TEdActionNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := Indent + '<action modifier="' + fModKeys + '">' + #13#10;
 For I := 0 to Count - 1 do begin
  Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
 end;
 Result := Result + Indent + '</action>' + #13#10;
End;

{ TEdExecuteNode }

constructor TEdExecuteNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 inherited;
 fnodetype := ntExecute;
 name := 'execute';
end;

procedure TEdExecuteNode.LoadSelf(FromNode: IXMLNode);
begin
 If FromNode.ChildNodes.count = 1 then begin
  If uppercase(FromNode.ChildNodes[0].NodeName) = '#TEXT' then begin
   fExecute := FromNode.ChildNodes[0].NodeValue;
  end;
 end
  else
 begin
  LoadChildren(FromNode, Self);
 end;
end;

procedure TEdExecuteNode.SetupSelf(Execute: String);
begin
 fExecute := Execute;
end;

function TEdExecuteNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 If ContainsASensorChildNode then begin
  Result := Indent + '<execute>' + #13#10;
  For I := 0 to Count - 1 do begin
   Result := Result + fNodes[I].WriteXML(Indent + Options.Files.CMLIndent);
  end;
  Result := Result + Indent + '</execute>' + #13#10;
 end
  else
 begin
  Result := Indent + '<execute>' + fExecute + '</execute>' + #13#10;
 end;
End;

{ TEdCommentNode }

constructor TEdCommentNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 fOwner := Owner;
 fParent := Parent;
end;

procedure TEdCommentNode.LoadSelf(FromNode: IXMLNode);
begin
 CommentStr := FromNode.NodeValue;
end;

procedure TEdCommentNode.SetupSelf(CommentString: String);
begin
 CommentStr := CommentString;
end;

function TEdCommentNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
begin
 Result := Indent + '<!--' + CommentStr + '-->' + #13#10;
end;

{ TEdRootNode }

constructor TEdRootNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 fOwner := Owner;
 fParent := Parent;
end;

function TEdRootNode.WriteXML(Indent: String = ''; ObjectOnly: boolean = false): String;
Var
 I: Integer;
begin
 Result := '';
 For I := 0 to Count - 1 do begin
  Result := Result + Node[I].WriteXML;
 end;
end;

{ TEdLinkedNode }

constructor TEdLinkedNode.Create(Owner: TEdCM2Tree; Parent: TEdRawNode);
begin
 fOwner := Owner;
 fParent := Parent;
 fNodeType := ntLinked;
end;

end.
