////////////////////////////////////////////////////////////////////////////////
// Name: tcpObjectInspector
// Last Revision: September 15th, 2012
// Version: 4.4
//
// Copyright © 2006 - 2012 by Christian Aaangel
////////////////////////////////////////////////////////////////////////////////
// What's new
//
// Version 4.4
//  * To avoid flicker Group State, Highlight State and Key State has been
//    altered to only trigger if the new value is different from the current.
//  * TtcpOI.SetParameterValue has been changed to a function, returning TRUE
//    if the requested Parameter was found 
//
// Version 4.3
//  * Added PerformForceUpdate to TtcpOI, because system is designed not to
//    to fire Updates when creating. This can be used for Syntax code on
//    dynamicly created OI's, after EndUpdate
//
// Version 4.2
//  * Added Check/Uncheck using ENTER in the Checkbox type
//  * Defaulting Calender to Now if value is empty
//
// Version 4.1
//  * Added the function "CloneGUI" to the TtcpOI.
//    It will allow you to clone colors, keywidth and instantupdate from another
//    TtcpOI object.
//  * Added the ability to highlight a key.
//
////////////////////////////////////////////////////////////////////////////////
// Ideas
//
// * A chain image alongside the +/- to select whether or not the splitter will
//   work locally or globally
////////////////////////////////////////////////////////////////////////////////
// Bugs
//
// * The TMemo decendant isn't entirely intuitive. The delete key isn't working
//   as one would expect.
//
////////////////////////////////////////////////////////////////////////////////
// Missing
//
// * A class where a "..." button will trigger a notifyevent for eddie to
//   trigger it's dialogs
////////////////////////////////////////////////////////////////////////////////
//
// NOTE
//
// Using the SETPARAMETERVALUE will fire a "OnParameterUpdate" event.
// It's done that way to allow you to enable/disable controls when a value is
// updated.
//
// The object inspector has a property InstantUpdate, when set to TRUE
// any change in the OI will fire an "OnParameterUpdate" event, instead of
// waiting for an Enter Keypress or moving out of the OI field.
//
////////////////////////////////////////////////////////////////////////////////
//
// Parameters for different types
//
// itEdit - 'MAXCHAR=X|ILLEGAL=(),.'
//          MAXCHAR defines the max CHAR length of the edit.
//           eg.
//           MAXCHAR=10 - the user will only be able to enter 10 chars.
//           MAXCHAR=4 - the user will only be able to enter 4 chars.
//          ILLEGAL is a list of CHARs that cannot be used in the edit.
//
// itTrack - 'MIN=XXX|MAX=YYY' - Define lower and upper limits for the value
//
//
// NOTE FOR COMBO TYPES!
// You cannnot create an Item named 'THECOMBOBOXSTYLE'. Since it's used internally
//
// itComboFixed - 'Item1|Item2|Item3' - These will be selectable in the dropdown
//                NOTE: If the value isn't in the dropdown list, nothing will
//                be selected. So you might want to add some error checking.
//
// itCombo - 'MAXCHAR=X|ILLEGAL=(),.|Item1|Item2|Item3' - MAXCHAR sets the max text length
//           ILLEGAL defines chars that are illegal for use.
//           Anything after MAXCHAR will be added to the dropdownlist.
//           NOTE: You cannot use '=' in the item names.
//
// itRadio - 'RadioItem1|RadioItem2|RadioItem3' - These will be created as
//           buttons
//
// itCheck - 'TRUE=XXX|FALSE=YY' - Allows you to set the strings which will
//           determine checked/unchecked.
//           Eg.
//           'TRUE=Yes|FALSE=No' - If VALUE is 'Yes' then the box will be checked
//           'TRUE=Right|FALSE=Wrong' - if VALUE is 'Right' then the box will be checked
//
// itCalendar - Doesn't take parameters
//
// itNumeric - 'Seperator=,|Decimal=2|MAXCHAR=10' - Seperator is the decimal seperator
//             (Seperator may be shorten to 'SEP=,'). Decimal is how many decimals
//             You'll allow after the seperator.
//             This control will only allow the number 0 - 9 and the Seperator to
//             be entered. And only the approved amount of decimals after the
//             seperator.
//             MAXCHAR - The max CHAR length of the string
//             If you don't want to allow a seperator set DECIMAL=0
//
// itMemo - 'MaxLines=3|MaxChar=10|ILLEGAL=(),.' - Illegal is a list of CHARs that can't be used.
//          MaxLines will allow you to set max lines
//          Allowed in the memo. MaxChar will allow you to set maximum Chars per line
//
////////////////////////////////////////////////////////////////////////////////
// How to write a new Item Class
//
// First you make a decendant of TtcpItemPanel, then you need to read the
// following steps in order to make it work correctly
//
// IMPORTANT: The property "InstantUpdate" under the OI is handled by Original
//            Polymorphic class, but it's controlled by the MODIFIED property.
//            So be sure to set it correctly.
//
// * Add the new type to TtcpOIItemType (Enumerator type)
//   You also have to ADD it to the creation process in
//   TtcpOIPanel.RebuildControls
//   [FAILURE TO ADD] Your new type will not be available
//
// * In TtcpOIPanel.RebuildControls make sure you add the new Itemtype to the
//   creation process
//   [FAILURE TO ADD] Your new type will not be created when needed, instead
//                    the default panel ("abstract" version) will be created.
//
// * Override "OnValuePanelResize"
//   It will be ran when the Value Panel is resized, so you'll have to
//   redimension your controls lying ontop of it.
//   [FAILURE TO ADD] Your Item will not look right and only work when the
//                    OI has a certain size
//
// * When you need to update a value in the OI (Codewise, like when the user
//   press enter), use this function "OwnerPanel.OI.UpdateParameter"
//   Remember to check if the value has been changed after the function, the
//   OI offers an event to allow the programmer to override the change, this
//   has to be in there in order to check for illegal statements
//   [FAILURE TO ADD] Your value will never be altered, remember the OI is a
//                    superficial layer, so the programmer will not be informed
//                    that he/her needs to alter the value in the original place
//
// * The value property corresponds to GETVALUE (function) and SETVALUE
//   (procedure), these you will have to override and then write your own.
//   GETVALUE is called when the OI needs to know what value the parameter has
//   SETVALUE is called when the OI needs to change the value, usually after
//            a user command changing a value already showing.
//   [FAILURE TO ADD] The user will not see a true represetation of the object
//
// * Procedure Select
//   This one is used when your Item needs to take focus. So override the
//   original and make sure you give your focus object focus. Also make sure
//   you track the keystrokes of the up/down arrow. When the user press the up
//   arrow key, you need to run OwnerPanel.SelectPrevious(self), when the user
//   press the down arrow run OwnerPanel.SelectNext(self). This will allow the
//   user to step through the OI using the arrow keys, the system is designed so
//   the OI itself will find the next one in line (Of the visible ones)
//   [FAILURE TO ADD] The user will not be able to use the arrow keys to step
//                    around in the OI.
//
// * Coloring
//   There are 2 color settings that your type needs to adhere to, background
//   and text color, both are found under the OI class,
//   OwnerPanel.OI.ItemBackground -and-
//   OwnerPanel.OI.ItemFontColor
//   [FAILURE TO ADD] Your item will stand out from the coloring of the OI
//		                In General
//
// * Procedure Enable
//   Procedure is used when your control is to set itself to enabled.
//   NOTE: Make sure you set "IsEnabled" variable to TRUE in this procedure.
//   [FAILURE TO ADD] Your control will remain enabled when it's supposed to be
//                    Disabled.
//   [FAILURE TO INHERIT] Your Item will not be colored correctly when disabled.
//
// * Procedure Disable
//   Procedure is used when your control is to set itself to disabled.
//   NOTE: Make sure you set "IsEnabled" variable to FALSE in this procedure.
//   [FAILURE TO ADD] Your control will remain enabled when it's supposed to be
//                    Disabled.
//   [FAILURE TO INHERIT] Your Item will not be colored correctly when disabled.
//
// * Drag & Drop Support
//   In order to make it easier for the user the value field must also support
//   drag and drop equally as name panel. This is rather easy to accomplish.
//   On your custom controls, set OnDragDrop and OnDragOver event to the
//   Respective procedure of same name already defined in the ancestor class.
//   This will automatically enable drag and drop over value field.
//   [FAILURE TO ADD] Drag and drop are only supported on the Namepanel and
//                    the valuepanel.
////////////////////////////////////////////////////////////////////////////////
unit tcpObjectInspector;

interface

Uses ExtCtrls, Classes, Controls, StdCtrls, SysUtils, Forms, ComCtrls, graphics,
 Messages, Windows, Dialogs, Variants;

Type
 // PRE DECLARE PROTOTYPES
 TtcpItemPanel = Class;
 TtcpOIPanel = Class;
 TtcpOI = Class;
 //
 TtcpOIItemType = (itUnknown, itEdit, itTrack, itComboFixed, itRadio, itCheck,
                   itCalendar, itNumeric, itCombo, itMemo, itPicture);
 TtcpOIItem = Record
  Name: String;
  Value: String;
  ItemType: TtcpOIITemType;
  Group: String;
  Parameters: String;
 end;

 TtcpCheckbox = Class(TCustomCheckBox)
 public
  procedure WMGetDlgCode(var Message: TWMGETDLGCODE); message WM_GETDLGCODE;
  Property OnEnter;
  Property OnExit;
  Property Checked;
  Property Caption;
  Property Color;
  Property OnKeyDown;
  Property OnKeyUp;
 end;

 TtcpRadiobutton = Class(TRadiobutton)
 public
  procedure WMGetDlgCode(var Message: TWMGETDLGCODE); message WM_GETDLGCODE;
  Property OnEnter;
  Property OnExit;
  Property Checked;
  Property Caption;
  Property Color;
  Property OnKeyDown;
  Property OnKeyUp;
  Property OnClick;
//  Property 
 end;

 // List class to handle the backbone
 TtcpOIItemList = class(Tobject)
 private
  fList: Array of TtcpOIItem;
  FOnUpdate: TNotifyEvent;
  fParent: TComponent;
  IsUpdating: Boolean;
  function GETCOUNT: Integer;
  procedure SetOnUpdate(const Value: TNotifyEvent);
  Procedure TriggerUpdateEvent;
  function GETITEM(Index: Integer): TtcpOIItem;
  procedure SETITEM(Index: Integer; const Value: TtcpOIItem);
 public
  Constructor Create(ParentCont: TComponent);
  Procedure Add(Name, Value, Group, Parameters: String; ItemType: TtcpOIItemType);
  Procedure Clear;
  Procedure Delete(Index: Integer);
  Property Count: Integer read GETCOUNT;
  Property OnUpdate: TNotifyEvent read FOnUpdate write SetOnUpdate;
  Property Parent: TComponent read fParent;
  Property Item[Index: Integer]: TtcpOIItem read GETITEM write SETITEM; default;
  Procedure BeginUpdate;
  Procedure EndUpdate;
 end;

 // Visual class to create an Item ... this one is "Abstract" (IE. no edit field) --> polymorph
 TtcpItemPanel = Class(TCustomPanel)
 private
  IsEnabled: Boolean;
  IsSelected: Boolean;
  NamePanel: TPanel;
  ValuePanel: TPanel;
  Splitter: TSplitter;
  NamePanelLabel: TLabel;
  fOwnerPanel: TtcpOIPanel;
  fItemName: String;
  FItemValue: string;
  Item: TtcpOIItem;
  fModified: Boolean;
    FIsHighlighted: Boolean;
  Procedure OnValuePanelResize(Sender: TObject); Virtual;
  procedure SetItemValue(const Value: string);
  Procedure OnSplitterMove(Sender: Tobject);
  function GETVALUE: String; virtual;
  procedure SETVALUE(const Value: String); virtual;
  Procedure OnDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
  procedure OnDragDrop(Sender, Source: TObject; X, Y: Integer);
  procedure SETMODIFIED(const Value: Boolean);
  function GETMODIFIED: Boolean;
    procedure SetIsHighlighted(const Value: Boolean);
 public
  Constructor create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce; virtual;
  Destructor Destroy; reintroduce; virtual;
  Procedure Select(AsNextInOrder: Boolean); virtual;
  Procedure PaintSelected;
  Procedure PaintUnSelected;
  Procedure Enable; virtual;
  Procedure Disable; virtual;
  Procedure SendUpdate(ForceUpdate: Boolean = False); Virtual;
 published
  Property OwnerPanel: TtcpOIPanel read fOwnerPanel;
  Property ItemName: String read fItemName;
  Property ItemValue: string read FItemValue write SetItemValue;
  Property Modified: Boolean read GETMODIFIED write SETMODIFIED;
  Property Left;
  Property Top;
  Property Height;
  Property Width;
  Property Align;
  Property Value: String read GETVALUE write SETVALUE;
  Property IsHighlighted: Boolean read FIsHighlighted write SetIsHighlighted;
 end;

 // Class to show a group in the OI
 TtcpOIPanel = Class(TcustomPanel)
 private
  fList: Array of TtcpItemPanel;
  FExpanded: Boolean;
  FGroupName: String;
  TopPanel: TPanel;
  GutterPanel: TPanel;
  ClientPanel: TPanel;
  TriggerImg: TImage;
  GroupNameLabel: TLabel;
  fOI: TtcpOI;
  procedure SetExpanded(const Value: Boolean);
  procedure DoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  procedure SetGroupName(const Value: String);
  procedure OnItemListUpdate(Sender: TObject);
 Protected
  Procedure Paint; override;
 public
  ItemList: TtcpOIItemList;
  Function SetParameterValue(ParameterName, ParameterValue: String): Boolean;
  Function SetParameterEnabled(ParameterName: String; NewState: Boolean): Boolean;
  Function SetParameterHighlight(ParameterName: String; Highlight: Boolean): Boolean;
  Function GetParameterHighlight(ParameterName: String): Boolean;
  Procedure PerformForceUpdate; 
  Procedure ResizeName(Value: Integer);
  Procedure RebuildControls;
  Property Expanded: Boolean read FExpanded write SetExpanded;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOI); reintroduce;
  Procedure SelectPrevious(Current: TtcpItemPanel);
  Procedure SelectNext(Current: TtcpItemPanel);
  Procedure SelectFirst;
  Procedure SelectLast; 
 Published
  Property Left;
  Property Height;
  Property Width;
  Property Top;
  Property Color;
  Property GroupName: String read FGroupName write SetGroupName;
  Property OI: TtcpOI read fOI;
 end;

 // Class to store the extended state of all groups
 TtcpOIEx = Record
  GroupName: String;
  Extended: Boolean;
 end;
 TtcpOIExList = Class(TObject)
 private
  fList: Array of TtcpOIEx;
    function GETCOUNT: integer;
    function GETITEM(Index: Integer): TtcpOIEx;
    procedure SETITEM(Index: Integer; const Value: TtcpOIEx);
 public
  Procedure Add(GroupName: String; NewExState: Boolean);
  Procedure Delete(GroupName: String);
  Function GetState(GroupName: String): Boolean;
  Property Count: integer READ GETCOUNT;
  Property Items[Index: Integer]: TtcpOIEx READ GETITEM WRITE SETITEM; Default;
  Procedure Save;
  Procedure Load;
  Procedure Clear;
 end;


 // Main OI Class
 TtcpOIOnParameterUpdate = procedure(Name: String; Var Value: String; Parameters: string) of Object;
 TtcpItemDragOver = Procedure(ParameterName: String; Source: TObject;
                               X, Y: Integer; State: TDragState; var Accept: Boolean) of Object;
 TtcpItemDragDrop = Procedure(ParameterName: String; Source: TObject) of Object;
 TtcpOI = Class(TScrollBox)
 private
  fList: Array of TtcpOIPanel;
  fExList: TtcpOIExList;
  FOnParameterUpdate: TtcpOIOnParameterUpdate;
  FOnItemDragDrop: TtcpItemDragDrop;
  FOnItemDragOver: TtcpItemDragOver;
  FGroupCaptionText: TColor;
  FItemFontColor: TColor;
  FItemBackground: TColor;
    FGroupBackground: Tcolor;
    FDragFailColor: TColor;
    FDragAcceptColor: TColor;
    FSelectionColor: TColor;
    FSelectionFontColor: Tcolor;
    FKeyWidth: Integer;
    FDisabledColor: TColor;
    FDisabledFontColor: TColor;
    FInstantUpdate: Boolean;
    FHighlightSelectionFontColor: TColor;
    FHighlightBackground: TColor;
    FHighlightSelectionColor: TColor;
    FHighlightFontColor: TColor;
  Procedure OnItemUpdate(Sender: TObject);
  Procedure RebuildControls;
  procedure SetOnParameterUpdate(const Value: TtcpOIOnParameterUpdate);
  Procedure UpdateParameter(N: String; Var V: String; Parameters: String);
  procedure SetOnItemDragDrop(const Value: TtcpItemDragDrop);
  procedure SetOnItemDragOver(const Value: TtcpItemDragOver);
  procedure SetGroupCaptionText(const Value: TColor);
  procedure SetItemBackground(const Value: TColor);
  procedure SetItemFontColor(const Value: TColor);
    procedure SetGroupBackground(const Value: Tcolor);
    procedure SetDragAcceptColor(const Value: TColor);
    procedure SetDragFailColor(const Value: TColor);
    procedure SetSelectionColor(const Value: TColor);
    procedure SetSelectionFontColor(const Value: Tcolor);
    procedure SetKeyWidth(const Value: Integer);
    procedure SetDisabledColor(const Value: TColor);
    procedure SetDisabledFontColor(const Value: TColor);
    procedure SetInstantUpdate(const Value: Boolean);
    procedure SetHighlightBackground(const Value: TColor);
    procedure SetHighlightFontColor(const Value: TColor);
    procedure SetHighlightSelectionColor(const Value: TColor);
    procedure SetHighlightSelectionFontColor(const Value: TColor);
 public
  ItemList: TtcpOIItemList;
  Groups: TStringList;
  Constructor Create(aOwner: TComponent); override;
  Procedure SetNameLength(Value: Integer);
  Function SetParameterValue(ParamName: String; Value: String): Boolean;
  Function SetKeyEnabledState(ParamName: String; Enabled: Boolean): Boolean;
  Function SetGroupEnabledState(GroupName: String; Enabled: Boolean): Boolean;
  Function SetKeyHighlightedState(ParamName: String; Highlight: Boolean): Boolean;
  Function GetKeyHighlightedState(ParamName: String): Boolean;
  // PerformForceUpdate will trigger a OnParameterUpdate on all children.
  Procedure PerformForceUpdate;
  Procedure SelectNext(Current: TtcpOIPanel);
  Procedure SelectPrevious(Current: TtcpOIPanel);
  Procedure SetFocus; override;
  Procedure ChangeExState(GroupName: String; NewExState: Boolean);
  Function GetExState(GroupName: String): Boolean;

  // The CloneGUI procedure will allow you to clone the settings of another
  // instance of a TtcpOI object.
  // SourceOI is the Object inspector control you want to clone from.
  // Colors - ALL colors will be clone.
  // KeySize - The Keywidth size will be cloned
  // InstantUpdate - The instant update state will be cloned
  Procedure CloneGUI(SourceOI: TtcpOI; Colors: Boolean = True; KeySize: Boolean = True; InstantUpdate: Boolean = True);
 Published
  // Colors
  Property GroupCaptionText: TColor read FGroupCaptionText write SetGroupCaptionText default clHighlightText;
  Property GroupBackground: Tcolor read FGroupBackground write SetGroupBackground;
  Property ItemBackground: TColor read FItemBackground write SetItemBackground default clBtnFace;
  Property ItemFontColor: TColor read FItemFontColor write SetItemFontColor default clMenuText;
  Property SelectionColor: TColor read FSelectionColor write SetSelectionColor;
  Property SelectionFontColor: Tcolor read FSelectionFontColor write SetSelectionFontColor;
  Property DragAcceptColor: TColor read FDragAcceptColor write SetDragAcceptColor;
  Property DragFailColor: TColor read FDragFailColor write SetDragFailColor;
  Property DisabledColor: TColor read FDisabledColor write SetDisabledColor;
  Property DisabledFontColor: TColor read FDisabledFontColor write SetDisabledFontColor;
  Property HighlightBackground: TColor read FHighlightBackground write SetHighlightBackground;
  Property HighlightFontColor: TColor read FHighlightFontColor write SetHighlightFontColor;
  Property HighlightSelectionColor: TColor read FHighlightSelectionColor write SetHighlightSelectionColor;
  Property HighlightSelectionFontColor: TColor read FHighlightSelectionFontColor write SetHighlightSelectionFontColor;
  // Size
  Property KeyWidth: Integer read FKeyWidth write SetKeyWidth; // Size of Key | Value (position of splitter)
  // Instant Update - Will update an item with each change rather than
  // when enter is pressed or up/down is pressed.
  Property InstantUpdate: Boolean read FInstantUpdate write SetInstantUpdate;
  // Events
  Property OnParameterUpdate: TtcpOIOnParameterUpdate read FOnParameterUpdate write SetOnParameterUpdate;
  Property OnItemDragOver: TtcpItemDragOver read FOnItemDragOver write SetOnItemDragOver;
  Property OnItemDragDrop: TtcpItemDragDrop read FOnItemDragDrop write SetOnItemDragDrop;
 end;

 // Standard Edit field Item class
 TtcpItemPanelEdit = Class(TtcpItemPanel)
 private
  Edit: TEdit;
  Illegal: String;
//  Modified: Boolean;
  Procedure OnValuePanelResize(Sender: TObject); override;
  procedure OnEditKeyPress(Sender: TObject; var Key: Char);
  procedure OnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  function GETVALUE: String; override;
  procedure SETVALUE(const Value: String); override;
  Procedure OnEditExit(Sender: TObject);
  Procedure OnEditEnter(Sender: TObject);
  Procedure OnChange(Sender: TObject);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;

 end;
 // Trackbar + Edit field item class
 TtcpItemPanelTrack = Class(TtcpItemPanel)
 private
  Edit: TEdit;
  Track: TTrackbar;
  LastKeyIsUp: Boolean;
  LastKeyIsDown: Boolean;
//  Modified: Boolean;
  Procedure OnValuePanelResize(Sender: TObject); override;
  procedure OnEditKeyPress(Sender: TObject; var Key: Char);
  Procedure OnTrackChange(Sender: TObject);
  function GETVALUE: String; override;
  procedure SETVALUE(const Value: String); override;
  procedure OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  procedure OnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  Procedure OnEditExit(Sender: TObject);
  Procedure OnEditEnter(Sender: TObject);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;
 // Dropdown
 TtcpItemCombobox = Class(TtcpItemPanel)
 private
  Combo: TCombobox;
  Illegal: String;
//  Modified: Boolean;
  LastKeyIsUp: Boolean;
  LastKeyIsDown: Boolean;
  Procedure OnComboChange(Sender: TObject);
  Procedure OnValuePanelResize(Sender: TObject); override;
  Function GETVALUE: String; override;
  Procedure SETVALUE(Const Value: String); override;
  Procedure OnKeyDown(Sender: TObject; Var key: Word; Shift: TShiftState);
  Procedure OnKeyUp(Sender: TObject; Var Key: Word; Shift: TShiftState);
  Procedure OnEnter(Sender: TObject);
  Procedure OnExit(Sender: TObject);
  procedure OnKeyPress(Sender: TObject; var Key: Char);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Disable; override;
  Procedure Enable; override;
 end;
 // Radio buttons
 TtcpItemRadio = Class(TtcpItemPanel)
 private
  fList: Array of TtcpRadioButton;
  RadioList: TStringlist;
//  Modified: boolean;
  Selected: Integer;
  Focused: Integer;
  LastKeyIsUp: Boolean;
  LastKeyIsDown: Boolean;
  Procedure FocusRadio(Index: Integer);
  Procedure OnKeyDown(Sender: TObject; Var Key: Word; Shift: TShiftState);
  Procedure OnKeyUp(Sender: TObject; Var Key: Word; Shift: TShiftState);
  Procedure OnEnter(Sender: TObject);
  Procedure OnExit(Sender: TObject);
  Function GETVALUE: String; override;
  Procedure SETVALUE(Const Value: String); override;
  Procedure OnValuePanelResize(Sender: TObject); override;
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;
 // Checkbox
 TtcpItemCheck = Class(TtcpItemPanel)
 private
  Check: TtcpCheckBox;
//  Modified: Boolean;
  TrueStr: String;
  FalseStr: String;
  LastKeyIsUp: Boolean;
  LastKeyIsDown: Boolean;
  Procedure OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  Procedure OnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  PRocedure OnEnter(Sender: TObject);
  Procedure OnExit(Sender: TObject);
  Function GETVALUE: String; override;
  Procedure SETVALUE(Const Value: String); override;
  Procedure OnValuePanelResize(Sender: TObject); override;
  Procedure OnClick(Sender: TObject);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;
 // Calendar
 TtcpItemCalendar = Class(TtcpItemPanel)
 private
  DateTimePicker: TDateTimePicker;
//  Modified: Boolean;
  Procedure OnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  Procedure OnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  Procedure OnEnter(Sender: TObject);
  Procedure OnExit(Sender: TObject);
  procedure OnKeyPress(Sender: TObject; var Key: Char);
  Function GETVALUE: String; override;
  Procedure SETVALUE(Const Value: String); override;
  Procedure OnValuePanelResize(Sender: TObject); override;
  Procedure OnChange(Sender: TObject);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;
 // Numeric TEdit
 TtcpItemNumeric = Class(TtcpItemPanel)
 private
  Edit: TEdit;
//  Modified: Boolean;
  Sep: Char;
  Decimal: Integer;
  Procedure OnValuePanelResize(Sender: TObject); override;
  procedure OnKeyPress(Sender: TObject; var Key: Char);
  procedure OnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
  function GETVALUE: String; override;
  procedure SETVALUE(const Value: String); override;
  Procedure OnEditExit(Sender: TObject);
  Procedure OnEditEnter(Sender: TObject);
  Procedure OnChange(Sender: TObject);
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;
 // Memo
 TtcpItemMemo = Class(TtcpItemPanel)
 private
  Memo: TMemo;
  Illegal: String;
//  Modified: boolean;
  MaxRow: Integer;
  MaxChar: Integer;
  LastKeyIsUp: Boolean;
  LastKeyIsDown: Boolean;
  function RichCol: Longint;
  function RichRow: Longint;
  Function GetLine: Longint;
  Procedure OnKeyDown(Sender: TObject; Var Key: Word; Shift: TShiftState);
  Procedure OnKeyUp(Sender: TObject; Var Key: Word; Shift: TShiftState);
  procedure OnKeyPress(Sender: TObject; var Key: Char);
  Procedure OnEnter(Sender: TObject);
  Procedure OnExit(Sender: TObject);
  Function GETVALUE: String; override;
  Procedure SETVALUE(Const Value: String); override;
  Procedure OnValuePanelResize(Sender: TObject); override;
 public
  Procedure SendUpdate(Force: Boolean = False); override;
  Constructor Create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem); reintroduce;
  Destructor Destroy; override;
  Procedure Select(AsNextInOrder: Boolean); override;
  Procedure Enable; override;
  Procedure Disable; override;
 end;


implementation

uses filter, LPL_Sysutils;

Procedure NoParentColor(Panel: TPanel);
Begin
 Panel.ParentColor := False;
 Panel.ParentBackground := False;
 Panel.ParentFont := False;
end;

{ TtcpCheckbox }

procedure TtcpCheckbox.WMGetDlgCode(var Message: TWMGETDLGCODE);
begin
 Message.Result := DLGC_WANTARROWS;
end;

{ TtcpRadiobutton }

procedure TtcpRadiobutton.WMGetDlgCode(var Message: TWMGETDLGCODE);
begin
 Message.Result := DLGC_WANTARROWS;
end;

{ TtcpOIPanel }

constructor TtcpOIPanel.Create(aOwner: TComponent; ParentCont: TtcpOI);
begin
 Inherited Create(aOwner);
// Self.DoubleBuffered := true;
 Self.Visible := false;
 If (aOwner is TWinControl) then begin
  Parent := (aOwner as TWinControl);
 end;
 FExpanded := true;
 fOI := ParentCont;
 Width := fOI.Width;

 ItemList := TtcpOIItemList.Create(self);
 ItemList.OnUpdate := OnItemListUpdate;
 TopPanel := TPanel.Create(Self);
 NoParentColor(TopPanel);
 TopPanel.Parent := Self;
 TopPanel.BevelInner := bvNone;
 TopPanel.BevelOuter := bvNone;
 TopPanel.Color := OI.GroupBackground;
// TopPanel.Font.Color := OI.GroupCaptionText;
 GutterPanel := TPanel.Create(Self);
 NoParentColor(GutterPanel);
 GutterPanel.Parent := Self;
 GutterPanel.BevelInner := bvNone;
 GutterPanel.BevelOuter := bvNone;
 GutterPanel.Color := OI.GroupBackground;
 ClientPanel := TPanel.Create(self);
 NoParentColor(GutterPanel);
 ClientPanel.Parent := self;
 ClientPanel.BevelInner := bvNone;
 ClientPanel.BevelOuter := bvNone;
 ClientPanel.Color := OI.ItemBackground;
// ClientPanel.DoubleBuffered := true;
 TopPanel.Align := alTop;
 TopPanel.Height := 21;
 GutterPanel.Align := alLeft;
 GutterPanel.Width := 20;
 ClientPanel.Align := alClient;

// GutterPanel.Color := clSkyblue;
// TopPanel.Color := clSkyblue;
 TopPanel.OnMouseDown := DoMouseDown;

 TriggerImg := TImage.Create(TopPanel);
 With TriggerImg do begin
  Parent := TopPanel;
  Top := 5;
  Left := 5;
  Height := 11;
  Width := 11;
  OnMouseDown := DoMouseDown;
 end;

 GroupNameLabel := TLabel.Create(TopPanel);
 With GroupNameLabel do begin
  Parent := TopPanel;
  left := 21;
  top := 4;
  Transparent := true;
  Font.Color := OI.GroupCaptionText;
  OnMouseDown := DoMouseDown;
 end;

 Expanded := False;
 Self.Visible := true;
end;

procedure TtcpOIPanel.DoMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
// If ((X >= 5) and (X <= 16)) and
 //   ((Y >= 5) and (Y <= 16)) then begin
  Expanded := Not Expanded;
  OI.ChangeExState(FGroupName, Expanded);
// end;
end;

function TtcpOIPanel.GetParameterHighlight(ParameterName: String): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to length(fList) - 1 do begin
  If uppercase(fList[I].ItemName) = Uppercase(ParameterName) then begin
   Result := fList[I].IsHighlighted;
  End;
 end;
end;

procedure TtcpOIPanel.OnItemListUpdate(Sender: TObject);
begin
 RebuildControls;
end;

procedure TtcpOIPanel.Paint;
begin
 inherited;
 With TriggerImg do begin
  Canvas.Brush.Color := clWhite;
  Canvas.Pen.Color := clBlack;
  Canvas.Rectangle(0,0,11,11);
  If FExpanded then begin
   Canvas.MoveTo(2,5);
   Canvas.LineTo(9,5);
  end
   else
  begin
   Canvas.MoveTo(2,5);
   Canvas.LineTo(9,5);
   Canvas.MoveTo(5,2);
   Canvas.LineTo(5,9);
  end;
 end;
 GroupNameLabel.Caption := GroupName;
end;

procedure TtcpOIPanel.PerformForceUpdate;
Var
 I: Integer;
begin
 For I := 0 to Length(fList) - 1 do begin
  // SendUpdate - Force the update (True)
  fList[I].SendUpdate(True);
 End;
end;

procedure TtcpOIPanel.RebuildControls;
Var
 H, I: Integer;
 Item: TtcpOIItem;
begin
 Self.Visible := false;
 For I := length(fList) - 1 downto 0 do begin
  FreeAndNil(fList[I]);
 end;
 SetLength(fList, ItemList.Count);
 H := 0;
 For I := 0 to ItemList.Count - 1 do begin
  Case ItemList[I].ItemType of
   itEdit: fList[I] := TtcpItemPanelEdit.create(ClientPanel, self, ItemList[I]);
   itTrack: fList[I] := TtcpItemPanelTrack.Create(ClientPanel, self, ItemList[I]);
   itComboFixed: Begin
                  // Manually add the type to the parameters since it uses the same
                  // control as itCombo
                  Item := ItemList[I];
                  Item.Parameters := 'THECOMBOBOXSTYLE=FIXED|' + Item.Parameters;
                  ItemList[I] := Item;
                  fList[I] := TtcpItemCombobox.Create(ClientPanel, self, ItemList[I]);
                 End;
   itCombo: Begin
             // Manually add the type to the parameters since it uses the same
             // control as itCombo
             Item := ItemList[I];
             Item.Parameters := 'THECOMBOBOXSTYLE=VARIABLE|' + Item.Parameters;
             ItemList[I] := Item;
             fList[I] := TtcpItemCombobox.Create(ClientPanel, self, ItemList[I]);
            End;
   itRadio: fList[I] := TtcpItemRadio.Create(ClientPanel, Self, ItemList[I]);
   itCheck: fList[I] := TtcpItemCheck.Create(ClientPanel, Self, ItemList[I]);
   itCalendar: fList[I] := TtcpItemCalendar.Create(ClientPanel, Self, ItemList[I]);
   itNumeric: fList[I] := TtcpItemNumeric.Create(ClientPanel, Self, ItemList[I]);
   itMemo: fList[I] := TtcpItemMemo.Create(ClientPanel, Self, ItemList[I]);
   else fList[I] := TtcpItemPanel.create(ClientPanel, self, ItemList[I]);
  end;
  fList[I].Align := alCustom;
  fList[I].Top := H;
  Inc(H, flist[I].Height);
 end;
 If ItemList.Count > 0 then begin
  If fExpanded then begin
   Height := H + TopPanel.Height;//TopPanel.Height + ItemList.Count * fList[0].Height;
  end;
 end;
 For I := 0 to length(fList) - 1 do begin
  If I > 0 then begin
   fList[I].Top := fList[I-1].Top + fList[I-1].Height + 15;
  end
   else
  begin
   fList[I].Top := 0;
  end;
  fList[I].Align := alTop;
  fList[I].TabOrder := I;
 end;
 Self.Visible := true;
 Repaint;
end;

procedure TtcpOIPanel.ResizeName(Value: Integer);
Var
 I: Integer;
begin
 For I := 0 to length(fList) - 1 do begin
  fList[I].NamePanel.Width := Value;
 end;
end;

procedure TtcpOIPanel.SelectFirst;
begin
 If Length(fList) = 0 then exit;
 fList[0].PaintSelected;
 fList[0].Select(true);
end;

procedure TtcpOIPanel.SelectLast;
begin
 If length(fList) = 0 then exit;
 fList[length(fList) - 1].PaintSelected;
 fList[length(fList) - 1].Select(false);
end;

procedure TtcpOIPanel.SelectNext(Current: TtcpItemPanel);
Var
 I: Integer;
begin
 Current.PaintUnSelected;
 For I := 0 to length(fList) - 1 do begin
  if Current = fList[I] then begin
   If (length(fList) - 1) >= (I + 1) then begin
    fList[I + 1].PaintSelected;
    fList[I + 1].Select(true);
    exit;
   end;
  end;
 end;
 OI.SelectNext(Self);
end;

procedure TtcpOIPanel.SelectPrevious(Current: TtcpItemPanel);
Var
 I: Integer;
begin
 Current.PaintUnSelected;
 For I := 0 to length(fList) - 1 do begin
  if Current = fList[I] then begin
   If I > 0 then begin
    fList[I - 1].PaintSelected;
    fList[I - 1].Select(false);
    exit;
   end;
  end;
 end;
 OI.SelectPrevious(Self);
end;

procedure TtcpOIPanel.SetExpanded(const Value: Boolean);
Var
 H, I: Integer;
begin
  // Exit routine if new value is equal to the old, to avoid flickering
  If FExpanded = Value then exit; 
  FExpanded := Value;
  If Value then begin
   // Paint expanded
   H := 0;
   For I := 0 to length(fList) - 1 do begin
    H := H + fList[I].Height;
   end;
   Height := H + TopPanel.Height;
   For I := 0 to length(fList) - 1 do begin
    fList[I].TabOrder := I;
    fList[I].Visible := true;
   end;
   For I := length(fList) - 1 downto 0 do begin
    fList[I].Top := 0;
   end;
  end
   else
  begin
   // Paint collapsed
   Height := Toppanel.Height;
   For I := 0 to length(fList) - 1 do begin
    fList[I].Visible := false;
   end;
  end;
  Repaint;
end;

procedure TtcpOIPanel.SetGroupName(const Value: String);
begin
  FGroupName := Value;
  If OI.GetExState(FGroupName) then begin
   Expanded := true;
  end;
  Repaint;
end;

{ TtcpOIItemList }
{
procedure TtcpOIItemList.Add(Name, Value: string;
  ItemType: TtcpOIItemType);
begin
 SetLength(fList, count + 1);
 fList[Count - 1].Name := Name;
 fList[Count - 1].Value := Value;
 fList[Count - 1].ItemType := ItemType;
 TriggerUpdateEvent;
end;

procedure TtcpOIItemList.Add(Name, Value, Group: string;
  ItemType: TtcpOIItemType);
begin
 SetLength(fList, count + 1);
 fList[Count - 1].Name := Name;
 fList[Count - 1].Value := Value;
 fList[Count - 1].ItemType := ItemType;
 fList[Count - 1].Group := Group;
 TriggerUpdateEvent;
end;
}
procedure TtcpOIItemList.Add(Name, Value, Group, Parameters: String;
  ItemType: TtcpOIItemType);
begin
 SetLength(fList, count + 1);
 fList[Count - 1].Name := Name;
 fList[Count - 1].Value := Value;
 fList[Count - 1].ItemType := ItemType;
 fList[Count - 1].Group := Group;
 fList[Count - 1].Parameters := Parameters;
 TriggerUpdateEvent;
end;

procedure TtcpOIItemList.BeginUpdate;
begin
 IsUpdating := true;
end;

procedure TtcpOIItemList.Clear;
begin
 Setlength(fList, 0);
 TriggerUpdateEvent;
end;

constructor TtcpOIItemList.Create(ParentCont: TComponent);
begin
 fParent := ParentCont;
 IsUpdating := false;
end;

procedure TtcpOIItemList.Delete(Index: Integer);
Var
 I: Integer;
begin
 For I := Index to Count - 2 do begin
  fList[I] := fList[I+1];
 end;
 Setlength(fList, count - 1);
 TriggerUpdateEvent;
end;

procedure TtcpOIItemList.EndUpdate;
begin
 IsUpdating := false;
 TriggerUpdateEvent;
end;

function TtcpOIItemList.GETCOUNT: Integer;
begin
 Result := Length(fList);
end;

function TtcpOIItemList.GETITEM(Index: Integer): TtcpOIItem;
begin
 Result := fList[Index];
end;

procedure TtcpOIItemList.SETITEM(Index: Integer; const Value: TtcpOIItem);
begin
 fList[Index] := Value;
end;

procedure TtcpOIItemList.SetOnUpdate(const Value: TNotifyEvent);
begin
  FOnUpdate := Value;
end;

procedure TtcpOIItemList.TriggerUpdateEvent;
begin
 If IsUpdating then exit;
 If Assigned(FOnUpdate) then begin
  FOnUpdate(self);
 end;
end;

Function TtcpOIPanel.SetParameterEnabled(ParameterName: String;
  NewState: Boolean): Boolean;
Var
 I: Integer;
begin
 Result := false;
 For I := 0 to length(fList) - 1 do begin
  If Uppercase(fList[I].ItemName) = Uppercase(ParameterName) then begin
   Result := true;
   If NewState then begin
    fList[I].Enable;
   end
    else
   begin
    fList[I].Disable;
   End;
  end;
 end;
end;

function TtcpOIPanel.SetParameterHighlight(ParameterName: String;
  Highlight: Boolean): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to length(fList) - 1 do begin
  If uppercase(fList[I].ItemName) = Uppercase(ParameterName) then begin
   fList[I].IsHighlighted := Highlight;
   Result := True;
  End;
 end;
end;

function TtcpOIPanel.SetParameterValue(ParameterName,
  ParameterValue: String): Boolean;
var
 I: Integer;
begin
 Result := False;
 For I := 0 to length(fList) - 1 do begin
  if uppercase(fList[I].ItemName) = uppercase(PArameterName) Then begin
   fList[I].Value := ParameterValue;
   Result := true;
  end;
 end;
end;

{ TtcpItemPanel }

constructor TtcpItemPanel.create(aOwner: TComponent; ParentCont: TtcpOIPanel; Item: TtcpOIItem);
begin
 Inherited Create(aOwner);
 IsEnabled := true;
 Self.Visible := false;
 If (aOwner is TWinControl) then begin
  Parent := (aOwner as TWinControl);
 end;
 FOwnerPanel := ParentCont;
 BevelOuter := bvSpace;
 Height := 21;
 Width := OwnerPanel.ClientPanel.Width;

 fItemName := Item.Name;
 ItemValue := Item.Value;
 Item := Item;

 Splitter := TSplitter.Create(self);
 ValuePanel := TPanel.Create(self);
 NoParentColor(ValuePanel);
 NamePanel := TPanel.Create(self);
 NoParentColor(NamePanel);

 NamePanel.Parent := Self;
 NamePanel.Align := alLeft;
 NamePanel.Width := OwnerPanel.OI.KeyWidth;
 NamePanel.Constraints.MinWidth := 25;
 NamePanel.Left := 0;
 NamePanel.BevelInner := bvNone;
 NamePanel.BevelOuter := bvNone;
 NamePanel.DoubleBuffered := true;
 NamePanel.OnDragOver := OnDragOver;
 NamePanel.OnDragDrop := OnDragDrop;
 NAmePanel.Color := OwnerPanel.OI.ItemBackground;

 NamePanelLabel := TLabel.Create(NamePanel);
 NamePanelLabel.ParentColor := False;
 NamePanelLabel.ParentFont := False;
 NamePanelLabel.Font.Color := OwnerPanel.OI.ItemFontColor;

 NamePanelLabel.Parent := NamePanel;
 NamePanelLabel.Left := 4;
 NamePanelLabel.Top := 2;
 NamePanelLabel.Caption := ItemName;
 NamePanelLabel.OnDragOver := OnDragOver;
 NamePanelLabel.OnDragDrop := OnDragDrop;

 Splitter.Parent := Self;
 Splitter.Beveled := true;
 Splitter.ResizeStyle := rsPattern;
 Splitter.Align := alLeft;
 Splitter.Left := NamePanel.Width;
 Splitter.OnMoved := OnSplitterMove;

 ValuePanel.Parent := Self;
 ValuePanel.Align := alClient;
 ValuePanel.Width := Width - NamePanel.Width - Splitter.Width;
 ValuePanel.Constraints.MinWidth := 25;
 ValuePanel.Left := Splitter.Left + Splitter.Width;
 ValuePanel.BevelInner := bvNone;
 ValuePanel.BevelOuter := bvLowered;
 ValuePanel.OnResize := OnValuePanelResize;
 ValuePanel.Color := OwnerPanel.OI.ItemBackground;
 ValuePanel.OnDragDrop := OnDragDrop;
 ValuePanel.OnDragOver := OnDragOver;

 Self.Visible := true;
end;

destructor TtcpItemPanel.Destroy;
begin

  inherited;
end;

procedure TtcpItemPanel.Disable;
begin
 If not IsEnabled then Exit; 
 // Set color on Key Panel
 NamePanel.Color := OwnerPanel.OI.DisabledColor;
 NamePanelLabel.Font.Color := OwnerPanel.OI.DisabledFontColor;
 NamePanelLabel.Font.Style := [fsStrikeOut];
end;

procedure TtcpItemPanel.Enable;
begin
 If IsEnabled then exit;
 // Set color on Key Panel
 NamePanel.Color := OwnerPanel.OI.ItemBackground;
 NamePanelLabel.Font.Color := OwnerPanel.OI.ItemFontColor;
 NamePanelLabel.Font.Style := [];
end;

function TtcpItemPanel.GETMODIFIED: Boolean;
begin
 Result := fModified;
end;

function TtcpItemPanel.GETVALUE: String;
begin
 Result := '';
end;

procedure TtcpItemPanel.OnDragDrop(Sender, Source: TObject; X, Y: Integer);
Var
 S: String;
begin
 If assigned(OwnerPanel.OI.OnItemDragDrop) then begin
  OwnerPanel.OI.OnItemDragDrop(ItemName, Source);
  S := GETVALUE;
  OwnerPanel.OI.UpdateParameter(ItemName, S, Item.Parameters);
  If S <> GETVALUE then begin
   SETVALUE(S);
  end;
 end;
end;

procedure TtcpItemPanel.OnDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
 If assigned(OwnerPanel.OI.OnItemDragOver) then begin
  OwnerPanel.OI.OnItemDragOver(ItemName, Source, X, Y, State, Accept);
 end
  else
 begin
  Accept := False;
 end;

 If State = dsDragEnter then begin
  If accept then begin
   NamePanel.Color := OwnerPanel.OI.DragAcceptColor;
  end
   else
  begin
   NamePanel.Color := OwnerPanel.OI.DragFailColor;
  end;
 end;
 If State = dsDragLeave then begin
  NamePanel.Color := OwnerPanel.OI.ItemBackground;
 end;
end;

procedure TtcpItemPanel.OnSplitterMove(Sender: Tobject);
begin
 If OwnerPanel.OI = nil then begin
  OwnerPanel.ResizeName(NamePanel.Width);
 end
  else
 begin
  OwnerPanel.OI.SetNameLength(NamePanel.Width);
 end;
end;

procedure TtcpItemPanel.OnValuePanelResize(Sender: TObject);
begin
 // do nothing
end;

procedure TtcpItemPanel.PaintSelected;
begin
 // if the control isn't enabled then exit
 If Not IsEnabled then exit;

 // Set the selected flag
 IsSelected := True;

 If Not IsHighlighted then begin
  // Item is not highlighted
  NamePanel.Color := OwnerPanel.OI.SelectionColor;
  NamePanelLabel.Font.Color := OwnerPanel.OI.SelectionFontColor;
 end
  else
 begin
  // Item is highlighted
  NamePanel.Color := OwnerPanel.OI.HighlightSelectionColor;
  NamePanelLabel.Font.Color := OwnerPanel.OI.HighlightSelectionFontColor;
 end;
end;

procedure TtcpItemPanel.PaintUnSelected;
begin
 // if the control isn't enabled then exit
 If Not IsEnabled then exit;

 // Set the Selected Flag
 IsSelected := False;
 
 If Not IsHighlighted then begin
  // Item is not highlighted
  NamePanel.Color := OwnerPanel.OI.ItemBackground;
  NamePanelLabel.Font.Color := OwnerPanel.OI.ItemFontColor;
 end
  else
 begin
  // Item is highlighted
  NamePanel.Color := OwnerPanel.OI.HighlightBackground;
  NamePanelLabel.Font.Color := OwnerPanel.OI.HighlightFontColor;
 end;
end;

procedure TtcpItemPanel.Select(AsNextInOrder: Boolean);
begin
 // do nothing
end;

procedure TtcpItemPanel.SendUpdate;
begin
 // do nothing
end;

procedure TtcpItemPanel.SetIsHighlighted(const Value: Boolean);
begin
  // Exit routine if the new value is equal to the old.
  If Value = FIsHighlighted then exit;
  FIsHighlighted := Value;
  If IsSelected then begin
   // Paint as selected
   PaintSelected;
  end
   else
  begin
   // Paint as unselected
   PaintUnSelected;
  end;
end;

procedure TtcpItemPanel.SetItemValue(const Value: string);
begin
  FItemValue := Value;
end;

procedure TtcpItemPanel.SETMODIFIED(const Value: Boolean);
begin
 fModified := Value;
 If Value then begin
  If OwnerPanel.OI.InstantUpdate then begin
   SendUpdate;
  End;
 End;
end;

procedure TtcpItemPanel.SETVALUE(const Value: String);
begin
 // do nothing
end;

{ TtcpOI }

procedure TtcpOI.ChangeExState(GroupName: String; NewExState: Boolean);
begin
 fExList.Add(GroupName, NewExState);
end;

procedure TtcpOI.CloneGUI(SourceOI: TtcpOI; Colors, KeySize,
  InstantUpdate: Boolean);
begin
 If Colors then begin
  GroupCaptionText := SourceOI.GroupCaptionText;
  GroupBackground := SourceOI.GroupBackground;
  ItemBackground := SourceOI.ItemBackground;
  ItemFontColor := SourceOI.ItemFontColor;
  SelectionColor := SourceOI.SelectionColor;
  SelectionFontColor := SourceOI.SelectionFontColor;
  DragAcceptColor := SourceOI.DragAcceptColor;
  DragFailColor := SourceOI.DragFailColor;
  DisabledColor := SourceOI.DisabledColor;
  DisabledFontColor := SourceOI.DisabledFontColor;
  HighlightBackground := SourceOI.HighlightBackground;
  HighlightFontColor := SourceOI.HighlightFontColor;
  HighlightSelectionColor := SourceOI.HighlightSelectionColor;
  HighlightSelectionFontColor := SourceOI.HighlightSelectionFontColor;
 End;

 If KeySize then begin
  KeyWidth := SourceOI.KeyWidth;
 End;

 If InstantUpdate then begin
  Self.InstantUpdate := SourceOI.InstantUpdate;
 end;
end;

constructor TtcpOI.Create(aOwner: TComponent);
begin
 Inherited Create(aOwner);
 If (aOwner is TWinControl) then begin
  Parent := (aOwner as TWinControl);
 end;
// Self.DoubleBuffered := true;
 ParentColor := True;
 ParentFont := False;

 GroupCaptionText := clWhite;
 GroupBackground := clSkyblue;
 ItemBackground := clMoneyGreen;
 ItemFontColor := clBlack;
 SelectionColor := clSkyblue;
 SelectionFontColor := clWhite;
 DragAcceptColor := clLime;
 DragFailColor := $00FF80FF;
 DisabledColor := clRed;
 DisabledFontColor := clWhite;
 HighlightBackground := clYellow;
 HighlightFontColor := clBlack;
 HighlightSelectionColor := clGreen;
 HighlightSelectionFontColor := clWhite;

 // Don't instant update by default.
 InstantUpdate := False;

 ItemList := TtcpOIItemList.Create(self);
 ItemList.OnUpdate := OnItemUpdate;
 Groups := TStringList.Create;
 Self.VertScrollBar.Tracking := true;

 fExList := TtcpOIExList.Create;
 fExList.Load;
end;

function TtcpOI.GetExState(GroupName: String): Boolean;
begin
 Result := fExList.GetState(Groupname);
end;

function TtcpOI.GetKeyHighlightedState(ParamName: String): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to length(fList) - 1 do begin
  Result := fList[I].GetParameterHighlight(ParamName);
  If Result then begin
   // Will exist is the parameter was found and it was highlighted
   Exit;
  end;
 end;
end;

procedure TtcpOI.OnItemUpdate(Sender: TObject);
begin
 RebuildControls;
end;

procedure TtcpOI.PerformForceUpdate;
Var
 I: Integer;
begin
 For I := 0 to Length(fList) - 1 do begin
  fList[I].PerformForceUpdate;
 End;
end;

procedure TtcpOI.RebuildControls;
Var
 I, II: Integer;
begin
 For I := length(fList) - 1 downto 0 do begin
  FreeAndNil(fList[I]);
 end;
 Groups.Clear;
 For I := 0 to ItemList.Count - 1 do begin
  IF Groups.IndexOf(ItemList[I].Group) = -1 then begin
   Groups.Add(ItemList[I].Group);
  end;
 end;
 Groups.Sort;
 Setlength(fList, Groups.Count);
 For I := 0 to Groups.count - 1 do begin
  fList[I] := TtcpOIPanel.Create(self, self);
  fList[I].Align := alTop;
  fList[I].GroupName := Groups[I];
  fList[I].ItemList.BeginUpdate;
  fList[I].ItemList.Clear;
  For II := 0 to ItemList.Count - 1 do begin
   If uppercase(Groups[I]) = uppercase(ItemList[II].Group) then begin
    fList[I].ItemList.Add(ItemList[II].Name, ItemList[II].Value, ItemList[II].Group, ItemList[II].Parameters, ItemList[II].ItemType);
   end;
  end;
  fList[I].ItemList.EndUpdate;
 end;
 For I := length(fList) - 1 downto 0 do begin
  fList[I].Top := 0;
 end;
 // I don't like the following approach but it seems to be the only way to make the vertical scrollbar appear on the OI
 // fList[0].Expanded := false;
 // fList[0].Expanded := true;
end;

procedure TtcpOI.SelectNext(Current: TtcpOIPanel);
Var
 Index, I: Integer;
begin
 Index := -1;
 For I := 0 to length(fList) - 1 do begin
  If fList[I] = current then begin
   Index := I;
   break;
  end;
 end;
 If Index = -1 then begin
  // Maybe an error dialog here
  exit;
 end;
 Repeat
  Inc(Index);
  if Index = length(fList) then Index := 0; // wrap around
 until fList[Index].Expanded;
 fList[Index].SelectFirst;
end;

procedure TtcpOI.SelectPrevious(Current: TtcpOIPanel);
Var
 Index, I: Integer;
begin
 Index := -1;
 For I := 0 to length(fList) - 1 do begin
  If fList[I] = current then begin
   Index := I;
   break;
  end;
 end;
 If Index = -1 then begin
  // Maybe an error dialog here
  exit;
 end;
 Repeat
  Dec(Index);
  if Index < 0 then Index := length(fList) - 1; // wrap around
 until fList[Index].Expanded;
 fList[Index].SelectLast;
end;

procedure TtcpOI.SetDisabledColor(const Value: TColor);
begin
  FDisabledColor := Value;
end;

procedure TtcpOI.SetDisabledFontColor(const Value: TColor);
begin
  FDisabledFontColor := Value;
end;

procedure TtcpOI.SetDragAcceptColor(const Value: TColor);
begin
  FDragAcceptColor := Value;
end;

procedure TtcpOI.SetDragFailColor(const Value: TColor);
begin
  FDragFailColor := Value;
end;

procedure TtcpOI.SetFocus;
begin
 Inherited;
 SelectFirst;
end;

procedure TtcpOI.SetGroupBackground(const Value: Tcolor);
begin
  FGroupBackground := Value;
end;

procedure TtcpOI.SetGroupCaptionText(const Value: TColor);
begin
  FGroupCaptionText := Value;
end;

Function TtcpOI.SetGroupEnabledState(GroupName: String; Enabled: Boolean): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to ItemList.Count - 1 do begin
  If uppercase(ItemList[I].Group) = uppercase(GroupName) then begin
   SetKeyEnabledState(ItemList[I].Name, Enabled);
  End;
 End;
 For I := 0 to Length(fList) - 1 do begin
  If uppercase(GroupName) = uppercase(fList[I].GroupName) then begin
   Result := True;
   fList[I].Expanded := Enabled;
  end;
 end;
end;

procedure TtcpOI.SetHighlightBackground(const Value: TColor);
begin
  FHighlightBackground := Value;
end;

procedure TtcpOI.SetHighlightFontColor(const Value: TColor);
begin
  FHighlightFontColor := Value;
end;

procedure TtcpOI.SetHighlightSelectionColor(const Value: TColor);
begin
  FHighlightSelectionColor := Value;
end;

procedure TtcpOI.SetHighlightSelectionFontColor(const Value: TColor);
begin
  FHighlightSelectionFontColor := Value;
end;

procedure TtcpOI.SetInstantUpdate(const Value: Boolean);
begin
  FInstantUpdate := Value;
end;

procedure TtcpOI.SetItemBackground(const Value: TColor);
begin
  FItemBackground := Value;
end;

procedure TtcpOI.SetItemFontColor(const Value: TColor);
begin
  FItemFontColor := Value;
end;

Function TtcpOI.SetKeyEnabledState(ParamName: String; Enabled: Boolean): Boolean;
Var
 I: Integer;
begin
 For I := 0 to length(fList) - 1 do begin
  If fList[I].SetParameterEnabled(ParamName, Enabled) then begin
   Result := True;
   // Speed optimize, will exit when it has found it.
   Exit;
  end;
 end;
 Result := False;
end;

Function TtcpOI.SetKeyHighlightedState(ParamName: String; Highlight: Boolean): Boolean;
Var
 I: Integer;
begin
 For I := 0 to length(fList) - 1 do begin
  If fList[I].SetParameterHighlight(ParamName, Highlight) then begin
   Result := True;
   // Speed optimize, will exit when it has found it.
   Exit;
  end;
 end;
 Result := False;
end;

procedure TtcpOI.SetKeyWidth(const Value: Integer);
begin
  FKeyWidth := Value;
end;

procedure TtcpOI.SetNameLength(Value: Integer);
Var
 I: Integer;
begin
 For I := 0 to length(fList) - 1 do begin
  fList[I].ResizeName(Value);
 end;
end;

procedure TtcpOI.SetOnItemDragDrop(const Value: TtcpItemDragDrop);
begin
  FOnItemDragDrop := Value;
end;

procedure TtcpOI.SetOnItemDragOver(const Value: TtcpItemDragOver);
begin
  FOnItemDragOver := Value;
end;

procedure TtcpOI.SetOnParameterUpdate(
  const Value: TtcpOIOnParameterUpdate);
begin
  FOnParameterUpdate := Value;
end;

Function TtcpOI.SetParameterValue(ParamName, Value: String): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to length(fList) - 1 do begin
  // Speed optimize, will exit when it has found it.
  If fList[I].SetParameterValue(ParamName, Value) then Begin
   Result := True;  
   exit;
  End;
 end;
end;

procedure TtcpOI.SetSelectionColor(const Value: TColor);
begin
  FSelectionColor := Value;
end;

procedure TtcpOI.SetSelectionFontColor(const Value: Tcolor);
begin
  FSelectionFontColor := Value;
end;

procedure TtcpOI.UpdateParameter(N: String; var V: String; Parameters: String);
begin
 If assigned(fOnParameterUpdate) then begin
  FOnParameterUpdate(N, V, Parameters);
 end;
end;

{ TtcpOIExList }

procedure TtcpOIExList.Add(GroupName: String; NewExState: Boolean);
Var
 I: Integer;
begin
 For I := 0 to count - 1 do begin
  if uppercase(groupname) = uppercase(fList[I].GroupName) then begin
   fList[I].Extended := NewExState;
   Save;
   exit;
  end;
 end;
 Setlength(fList, count + 1);
 fList[Count-1].GroupName := Groupname;
 fList[Count-1].Extended := NewExState;
 Save;
end;

procedure TtcpOIExList.Clear;
begin
 Setlength(fList, 0);
end;

procedure TtcpOIExList.Delete(GroupName: String);
Var
 I: Integer;
 At: Integer;
begin
 At := -1;
 For I := 0 to count - 1 do begin
  If uppercase(groupname) = uppercase(flist[I].GroupName) then begin
   At := I;
  end;
 end;
 // If not found ...
 If At = -1 then exit;
 // If found ...
 For I := At to count - 2 do begin
  fList[I] := fList[I+1];
 end;
 setlength(flist, count - 1);
 Save;
end;

function TtcpOIExList.GETCOUNT: integer;
begin
 Result := length(fList);
end;

function TtcpOIExList.GETITEM(Index: Integer): TtcpOIEx;
begin
 Result := fList[Index];
end;

function TtcpOIExList.GetState(GroupName: String): Boolean;
Var
 I: Integer;
begin
 Result := true;
 For I := 0 to count - 1 do begin
  if uppercase(groupname) = uppercase(fList[I].Groupname) then begin
   Result := fList[I].Extended;
   exit;
  end;
 end;
end;

procedure TtcpOIExList.Load;
Var
 I: Integer;
 S: String;
 B: Boolean;
 List: TStringlist;
begin
 Clear;
 If not fileexists(PathToOk(ExtractFilepath(ParamStr(0))) + 'oi.dat') then exit;
 List := Tstringlist.Create;
 List.LoadFromFile(PathToOk(ExtractFilepath(ParamStr(0))) + 'oi.dat');
 For I := 0 to list.count - 1 do begin
  S := List[I];
  If GetFilt(S, 2, '|') = '1' then B := True
   else B := False;
  S := GetFilt(S, 1, '|');
  Add(S, B);
 end;
 FreeAndNil(List);
end;

procedure TtcpOIExList.Save;
Var
 I: Integer;
 List: TStringList;
 S: String;
begin
 List := TStringlist.Create;
 For I := 0 to count - 1 do begin
  S := fList[I].GroupName;
  If fList[I].Extended then S := S + '|1'
   else S := S + '|0';
  List.Add(S);
 end;
 List.SaveToFile(PathToOk(ExtractFilepath(ParamStr(0))) + 'oi.dat');
 FreeAndNil(List);
end;

procedure TtcpOIExList.SETITEM(Index: Integer; const Value: TtcpOIEx);
begin
 fList[Index] := Value;
 Save;
end;


{ TtcpItemPanelEdit }

constructor TtcpItemPanelEdit.Create(aOwner: TComponent;
  ParentCont: TtcpOIPanel; Item: TtcpOIItem);
Var
 I: Integer;
 N,V: String;
begin
 inherited;
 Edit := TEdit.Create(ValuePanel);
 Edit.Parent := ValuePanel;
 Edit.Text := Item.Value;
 Edit.BorderStyle := bsNone;
 Edit.Top := 2;
 Edit.Left := 4;
 Edit.Height := 13;
 Edit.AutoSelect := true;
 Edit.Color := OwnerPanel.OI.ItemBackground;
 Edit.Font.Color := OwnerPanel.OI.ItemFontColor;
 OnValuePanelResize(self);
 Edit.OnKeyPress := OnEditKeyPress;
 Edit.OnKeyUp := OnKeyUp;
 Edit.OnExit := OnEditExit;
 Edit.OnEnter := OnEditEnter;
 Edit.OnChange := OnChange;
 Edit.OnDragDrop := OnDragDrop;
 Edit.OnDragOver := OnDragOver;
 For I := 1 to CountFilt(Item.Parameters, '|') do begin
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'MAXCHAR' then begin
   Try
    Edit.MaxLength := StrToInt(V);
   Except
    Edit.MaxLength := 0;
   End;
  end;
  If uppercase(N) = 'ILLEGAL' then begin
   Illegal := V + '|';
  end;
 End;
 Modified := false;
end;

destructor TtcpItemPanelEdit.Destroy;
begin
  FreeAndNil(Edit);
  inherited;
end;

procedure TtcpItemPanelEdit.Disable;
begin
 inherited;
 IsEnabled := false;
 Edit.Enabled := false;
end;

procedure TtcpItemPanelEdit.Enable;
begin
 inherited;
 IsEnabled := true;
 Edit.Enabled := true;
end;

function TtcpItemPanelEdit.GETVALUE: String;
begin
 Result := Edit.text;
end;

procedure TtcpItemPanelEdit.OnChange(Sender: TObject);
begin
 Modified := true;
end;

procedure TtcpItemPanelEdit.OnEditEnter(Sender: TObject);
begin
 PaintSelected;
end;

procedure TtcpItemPanelEdit.OnEditExit(Sender: TObject);
begin
 SendUpdate;
 PaintUnSelected;
end;

procedure TtcpItemPanelEdit.OnEditKeyPress(Sender: TObject; var Key: Char);
Var
 I: Integer;
begin
 If Key = #13 then begin
  Key := #0;
  Modified := True;
  SendUpdate;
  Exit;
 end;
 For I := 1 to Length(Illegal) do begin
  If Key = Illegal[I] then begin
   Key := #0;
   // Beep
   Exit;
  end;
 end;
 Modified := True;
end;

procedure TtcpItemPanelEdit.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 Case Key of
  VK_UP: Begin
          OwnerPanel.SelectPrevious(self);
         end;
  VK_DOWN: Begin
            OwnerPanel.SelectNext(self);
           end;
 end;
end;

procedure TtcpItemPanelEdit.OnValuePanelResize(Sender: TObject);
begin
 Edit.Width := ValuePanel.Width - 4;
end;

procedure TtcpItemPanelEdit.Select(AsNextInOrder: Boolean);
begin
 If IsEnabled then begin
  Edit.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemPanelEdit.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 N := ItemName;
 V := Edit.TExt;
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 If V <> Edit.Text then Edit.TExt := V;
 Modified := false; 
end;

procedure TtcpItemPanelEdit.SETVALUE(const Value: String);
begin
 Modified := True;
 Edit.Text := Value;
 SendUpdate;
end;

{ TtcpItemPanelTrack }

constructor TtcpItemPanelTrack.Create(aOwner: TComponent;
  ParentCont: TtcpOIPanel; Item: TtcpOIItem);
Var
 I: Integer;
 N, V: String;
begin
 inherited;
 Edit := TEdit.Create(ValuePanel);
 Edit.Parent := ValuePanel;
 Edit.Text := Item.Value;
 Edit.BorderStyle := bsNone;
 Edit.Top := 2;
 Edit.Left := 4;
 Edit.Height := 13;
 Edit.AutoSelect := true;
 Edit.OnKeyPress := OnEditKeyPress;
 Edit.Color := OwnerPanel.OI.ItemBackground;
 Edit.Font.Color := OwnerPanel.OI.ItemFontColor;
 Track := TTrackBar.Create(ValuePanel);
 Track.Parent := ValuePanel;
 Track.Top := 2;
 Track.Height := Track.Height - 5;
 Track.Left := 30;
 Track.TickStyle := tsNone;
 Track.ThumbLength := 13;
 For I := 1 to CountFilt(Item.Parameters, '|') do begin
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'MIN' then begin
   Try
    Track.Min := StrToInt(V);
   except
    Track.Min := 0;
   end;
  end;
  If uppercase(N) = 'MAX' then begin
   Try
    Track.Max := StrToInt(V);
   except
    Track.Max := 100;
   end;
  end;
 end;
 Try
  Track.Position := StrToInt(Item.Value);
 Except
  Track.Position := 0;
 end;
 OnValuePanelResize(self);
 Track.OnChange := OnTrackChange;
 Track.OnKeyDown := OnKeyDown;
 Track.OnKeyUp := OnKeyUp;
 Track.OnEnter := OnEditEnter;
 Track.OnExit := OnEditExit;
 Track.OnDragDrop := OnDragDrop;
 Track.OnDragOver := OnDragOver;
 Edit.OnKeyDown := OnKeyDown;
 Edit.OnKeyUp := OnKeyUp;
 Edit.OnEnter := OnEditEnter;
 Edit.OnExit := OnEditExit;
 Edit.OnDragDrop := OnDragDrop;
 Edit.OnDragOver := OnDragOver;
 LastKeyIsUp := False;
 LastKeyIsDown := False;
 Modified := false;
end;

destructor TtcpItemPanelTrack.Destroy;
begin
  FreeAndNil(edit);
  freeAndNil(Track);
  inherited;
end;

procedure TtcpItemPanelTrack.Disable;
begin
 inherited;
 IsEnabled := false;
 Edit.Enabled := False;
 Track.Enabled := false;
end;

procedure TtcpItemPanelTrack.Enable;
begin
 inherited;
 IsEnabled := true;
 Edit.Enabled := true;
 Track.Enabled := true;
end;

function TtcpItemPanelTrack.GETVALUE: String;
begin
 Result := IntToStr(Track.Position);
end;

procedure TtcpItemPanelTrack.OnEditEnter(Sender: TObject);
begin
 Modified := false;
 PaintSelected;
end;

procedure TtcpItemPanelTrack.OnEditExit(Sender: TObject);
begin
 SendUpdate;
 PaintUnSelected;
end;

procedure TtcpItemPanelTrack.OnEditKeyPress(Sender: TObject;
  var Key: Char);
Var
 Val: Integer;
 AllowUpdate: Boolean;
begin
 AllowUpdate := False;
 Case Ord(Key) of
  Ord('0') .. Ord('9'): Begin
//                         Edit.Text := Edit.TExt + Key;
                         Edit.SelText := Key;
//                         Edit.SelStart := Length(Edit.Text);
                         AllowUpdate := true;
                        end;
  8: Begin // Backspace
      Edit.Text := Copy(Edit.TExt, 1, length(Edit.text)- 1);
      AllowUpdate := True;
     End;
  13: Begin
       // Enter
       AllowUpdate := true;
      end;
 End;
 If AllowUpdate then begin
  Try
   Val := StrToInt(Edit.Text);
  except
   Edit.Text := '0';
   Val := 0;
  end;
  If Val > Track.Max then begin
   Val := Track.Max;
   Edit.Text := IntToStr(Val);
  end;
  If Val < Track.Min then begin
   Val := Track.Min;
   Edit.Text := IntToStr(Val);
  end;
  Track.Position := Val;
  Modified := true;
  Key := #0;
 end;
end;

procedure TtcpItemPanelTrack.OnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 LastKeyIsUp := False; LastKeyIsDown := false;
 Case Key of
  VK_UP: Begin
          Key := vk_add;
          LastKeyIsUp := true;
          exit;
         end;
  VK_DOWN: Begin
            Key := vk_add;
            LastKeyIsDown := true;
            exit;
           end;
 end;
end;

procedure TtcpItemPanelTrack.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 If LastKeyIsUp then begin
  Key := vk_add;
  LastKeyIsUp := False;
  OwnerPanel.SelectPrevious(self);
 end;
 If LastKeyIsDown then begin
  Key := vk_add;
  LastKeyIsDown := false;
  OwnerPanel.SelectNext(self);
 end;
end;

procedure TtcpItemPanelTrack.OnTrackChange(Sender: TObject);
begin
 Edit.Text := IntToStr(Track.Position);
 Modified := true;
end;

procedure TtcpItemPanelTrack.OnValuePanelResize(Sender: TObject);
begin
 Edit.Width := 25;
 Track.Width := ValuePanel.Width - Track.Left;
end;

procedure TtcpItemPanelTrack.Select(AsNextInOrder: Boolean);
begin
 Modified := false;
 If IsEnabled then begin
  Track.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;

end;

procedure TtcpItemPanelTrack.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
 Val: Integer;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 Edit.Text := IntToStr(Track.Position);
 N := ItemName;
 V := IntToStr(Track.Position);
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 If V <> Edit.Text then begin
  Try
   Val := StrToInt(V);
  Except
   Val := 0;
  end;
  Track.Position := Val;
 end;
 Modified := false;
end;

procedure TtcpItemPanelTrack.SETVALUE(const Value: String);
var
 Val: Integer;
begin
 Try
  Val := StrToInt(Value);
 except
  Val := 0;
 end;
 Track.Position := Val;
 Edit.Text := IntToStr(Val);
 Modified := True;
 SendUpdate;
end;

{ TtcpItemCombobox }

constructor TtcpItemCombobox.Create(aOwner: TComponent;
  ParentCont: TtcpOIPanel; Item: TtcpOIItem);
Var
 I: Integer;
 N, V: String;
 Handled: Boolean;
begin
 inherited;
 Combo := TCombobox.Create(ValuePanel);
 Combo.Parent := ValuePanel;
 Combo.Style := csDropDownList;
 Combo.Items.Clear;
 Combo.Color := OwnerPanel.OI.ItemBackground;
 Combo.Font.Color := OwnerPanel.OI.ItemFontColor;
 For I := 1 to CountFilt(Item.Parameters, '|') do begin
  Handled := False;
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'MAXCHAR' then begin
   Handled := true;
   Try
    Combo.MaxLength := StrToInt(V);
   except
    Combo.MaxLength := 0;
   End;
  end;
  If uppercase(N) = 'THECOMBOBOXSTYLE' then begin
   Handled := true;
   // These Parameters are added by the creation process depending on whether
   // itComboFixed or itCombo was selected.
   If uppercase(V) = 'VARIABLE' then begin
    Combo.Style := csDropDown;
   end;
   if uppercase(V) = 'FIXED' then begin
    Combo.Style := csDropDownList;
   end;
  end;
  If uppercase(N) = 'ILLEGAL' then begin
   Handled := true;
   Illegal := V + '|';
  end;
  If not Handled then begin
   Combo.Items.Add(N);
  end;
 end;
 Combo.ItemIndex := Combo.Items.IndexOf(Item.Value);
 Combo.OnChange := OnComboChange;
 Combo.OnEnter := OnEnter;
 Combo.OnExit := OnExit;
 Combo.OnKeyDown := OnKeyDown;
 Combo.OnKeyUp := OnKeyUp;
 Combo.OnKeyPress := OnKeyPress;
 Combo.OnDragDrop := OnDragDrop;
 Combo.OnDragOver := OnDragOver;
 OnValuePanelResize(self);
 Modified := False;
end;

destructor TtcpItemCombobox.Destroy;
begin
  FreeAndNil(Combo);
  inherited;
end;

procedure TtcpItemCombobox.Disable;
begin
 inherited;
 IsEnabled := False;
 Combo.Enabled := False;
end;

procedure TtcpItemCombobox.Enable;
begin
 inherited;
 IsEnabled := True;
 Combo.Enabled := True;
end;

function TtcpItemCombobox.GETVALUE: String;
begin
 Result := Combo.Items[Combo.ItemIndex];
end;

procedure TtcpItemCombobox.OnComboChange(Sender: TObject);
begin
 Modified := true;
end;

procedure TtcpItemCombobox.OnEnter(Sender: TObject);
begin
 Modified := False;
 PaintSelected;
end;

procedure TtcpItemCombobox.OnExit(Sender: TObject);
begin
 SendUpdate;
 PaintUnSelected;
end;

procedure TtcpItemCombobox.OnKeyDown(Sender: TObject; var key: Word;
  Shift: TShiftState);
begin
 LastKeyIsUp := False; LAstKeyIsDown := False;
 Case Key of
  VK_Down: begin
            if Combo.DroppedDown then exit;
            Key := vk_add;
            If (ssShift in Shift) then begin
             // Change selection
             If Combo.ItemIndex = Combo.Items.Count - 1 then begin
              Combo.ItemIndex := 0;
             end
              else
             begin
              Combo.ItemIndex := Combo.ItemIndex + 1;
             end;
             Modified := true;
            end
             else
            begin
             // Change Focus Item
             LastKeyIsDown := true;
             exit;
            end;
           end;
  VK_Up: begin
          if Combo.DroppedDown then exit;
          Key := vk_add;
          If (ssShift in Shift) then begin
           // Change selection
           If Combo.ItemIndex = 0 then begin
            Combo.ItemIndex := Combo.Items.Count - 1;
           end
            else
           begin
            Combo.ItemIndex := Combo.ItemIndex - 1;
           end;
           Modified := true;
          end
           else
          begin
           // Change Focus Item
           LastKeyIsUp := true;
           exit;
          end;
         end;
 end;
end;

procedure TtcpItemCombobox.OnKeyPress(Sender: TObject; var Key: Char);
Var
 I: Integer;
begin
 If Key = #13 then begin
  Key := #0;
  Modified := False;
  SendUpdate;
 end;
 For I := 1 to Length(Illegal) do begin
  If Key = Illegal[I] then begin
   Modified := true;
   Key := #0;
   // Beep
   Break;
  end;
 end;
end;

procedure TtcpItemCombobox.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 If LastKeyIsUp then begin
  Key := vk_add;
  OwnerPanel.SelectPrevious(self);
  exit;
 end;
 If LastKeyIsDown then begin
  Key := vk_Add;
  OwnerPanel.SelectNext(Self);
  Exit;
 end;
end;

procedure TtcpItemCombobox.OnValuePanelResize(Sender: TObject);
begin
 inherited;
 Combo.Left := 0;
 Combo.Width := ValuePanel.Width;
 Combo.Top := 0;
 Combo.Height := ValuePanel.Height;
end;

procedure TtcpItemCombobox.Select(AsNextInOrder: Boolean);
begin
 inherited;
 If IsEnabled then begin
  Combo.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemCombobox.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 N := ItemName;
 V := Combo.Items[Combo.ItemIndex];
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 If V <> Combo.Text then begin
  Combo.ItemIndex := Combo.Items.IndexOf(V);
 end;
 Modified := false;
end;

procedure TtcpItemCombobox.SETVALUE(const Value: String);
begin
 inherited;
 Modified := true;
 Combo.ItemIndex := Combo.Items.IndexOf(Value);
 SendUpdate;
end;

{ TtcpItemRadio }

constructor TtcpItemRadio.Create(aOwner: TComponent;
  ParentCont: TtcpOIPanel; Item: TtcpOIItem);
Var
 I: Integer;
begin
 inherited;
 RadioList := TStringlist.Create;
 For I := 1 to countfilt(item.Parameters, '|') do begin
  RadioList.Add(GetFilt(Item.PArameters, I, '|'));
 end;
 // It's unlikely that this will work, if the user is using a NON-STANDARD dpi setting.
 Height := (RadioList.Count * 17) + 6;
 ValuePanel.OnEnter := OnEnter;
 ValuePanel.OnExit := OnExit;
 Setlength(fList, Radiolist.Count);
 For I := 0 to RadioList.Count - 1 do begin
  fList[I] := TtcpRadioButton.Create(ValuePanel);
  fList[I].Parent := ValuePanel;
  fList[I].Color := OwnerPanel.OI.ItemBackground;
  fList[I].Font.Color := OwnerPanel.OI.ItemFontColor;
  fList[I].Left := 4;
  fList[I].Top := (I * 17) + 2;
  fList[I].Caption := RadioList[I];
//  fList[I].OnEnter := OnEnter;
//  fList[I].OnExit := OnExit;
  fList[I].OnKeyDown := OnKeyDown;
  fList[I].OnKeyUp := OnKeyUp;
  fList[I].OnDragDrop := OnDragDrop;
  fList[I].OnDragOver := OnDragOver;
//  fList[I].OnClick := OnClick;
  fList[I].Tag := I;
  If uppercase(fList[I].Caption) = uppercase(Item.Value) then begin
   fList[I].Checked := true;
   Selected := I;
  end;
 end;
 Focused := -1;
 OnValuePanelResize(self);
 LastKeyIsUp := FalsE;
 LastKeyIsDown := False;
 Modified := False;
end;

destructor TtcpItemRadio.Destroy;
Var
 I: Integer;
begin
  For I := length(FList) - 1 downto 0 do begin
   FreeAndNil(fList[I]);
  end;
  Setlength(fList, 0);
  FreeAndNil(RadioList);
  inherited;
end;

procedure TtcpItemRadio.Disable;
Var
 I: Integer;
begin
 inherited;
 IsEnabled := False;
 For I := 0 to Length(fList) - 1 do begin
  fList[I].Enabled := false;
 end;
end;

procedure TtcpItemRadio.Enable;
Var
 I: Integer;
begin
 inherited;
 IsEnabled := True;
 For I := 0 to Length(fList) - 1 do begin
  fList[I].Enabled := True;
 end;

end;

procedure TtcpItemRadio.FocusRadio(Index: Integer);
begin
 fList[Index].SetFocus;
 fList[Selected].Checked := true;
end;

function TtcpItemRadio.GETVALUE: String;
Var
 I: Integer;
begin
 Result := '';
 For I := 0 to length(fList) - 1 do begin
  If fList[I].Checked then begin
   Result := fList[I].Caption;
  end;
 end;
end;

procedure TtcpItemRadio.OnEnter(Sender: TObject);
begin
 PaintSelected;
end;

procedure TtcpItemRadio.OnExit(Sender: TObject);
begin
 PaintUnSelected;
 SendUpdate;
end;

procedure TtcpItemRadio.OnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 LastKeyIsUp := False; LastKeyIsDown := False;
 Case Key of
  vk_space: Begin
             Key := vk_Add;
             If (Sender is TRadiobutton) then begin
              (Sender as TRadiobutton).Checked := true;
              Selected := (Sender as TRadiobutton).Tag;
              Modified := true;
             end;
            end;
  vk_up: Begin
          Key := vk_add;
          If ssShift in Shift then begin

          end
           else
          begin
           LastKeyIsUp := true;
           Exit;
          end;
         end;
  vk_down: begin
            Key := vk_add;
            If ssShift in Shift then begin
            end
             else
            begin
             LastKeyIsDown := true;
             exit;
            end;
           end;
 end;
end;

procedure TtcpItemRadio.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 If LastKeyIsUp then begin
  Key := vk_add;
  If Focused = 0 then begin
   OwnerPanel.SelectPrevious(self);
  end
   else
  begin
   Dec(Focused);
   FocusRadio(Focused);
  end;
 end;
 If LastKeyIsDown then begin
  Key := vk_add;
  If Focused = (length(fList) - 1) then begin
   OwnerPanel.SelectNext(self);
  end
   else
  begin
   Inc(Focused);
   FocusRadio(Focused);
  end;
 end;
end;

procedure TtcpItemRadio.OnValuePanelResize(Sender: TObject);
var
 I: Integer;
begin
 inherited;
 For I := 0 to length(fList) - 1 do begin
  fList[I].Width := ValuePanel.Width - 4;
 end;
end;

procedure TtcpItemRadio.Select(AsNextInOrder: Boolean);
begin
 inherited;

 If IsEnabled then begin
  If AsNextInOrder then Focused := 0
   else Focused := Length(fList) - 1;
  FocusRadio(Focused);
  PaintSelected;
  Modified := false;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;

end;

procedure TtcpItemRadio.SendUpdate(Force: Boolean = False);
Var
 S, Val: String;
 I: Integer;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 Val := '';
 For I := 0 to length(FList) - 1 do begin
  If fList[I].Checked then begin
   Val := fList[I].Caption;
   break;
  end;
 end;
 If Val = '' then exit;
 S := Val;
 OwnerPanel.OI.UpdateParameter(ItemName, Val, Item.Parameters);
 If Val <> S then begin
  For I := 0 to length(fList) - 1 do begin
   If uppercase(Val) = uppercase(fList[I].Caption) then begin
    fList[I].Checked := true;
   end;
  end;
 end;
 Modified := False;
end;

procedure TtcpItemRadio.SETVALUE(const Value: String);
Var
 I: Integer;
begin
 inherited;
 Modified := true;
 For I := 0 to length(fList) - 1 do begin
  if uppercasE(value) = uppercase(fList[I].Caption) then begin
   fList[I].Checked := true;
  end;
 end;
 SendUpdate;
end;

{ TtcpItemCheck }

constructor TtcpItemCheck.Create(aOwner: TComponent;
  ParentCont: TtcpOIPanel; Item: TtcpOIItem);
Var
 I: Integer;
 N, V: String;
begin
 inherited;
 Check := TtcpCheckbox.Create(ValuePanel);
 Check.Parent := ValuePanel;
 Check.Top := 2;
 Check.Left := 4;
 Check.Width := 13;
 Check.Color := OwnerPanel.OI.ItemBackground;
 Check.Font.Color := OwnerPanel.OI.ItemFontColor;
 Check.OnEnter := OnEnter;
 Check.OnExit := OnExit;
 Check.OnKeyDown := OnKeyDown;
 Check.OnKeyUp := OnKeyUp;
 Check.OnClick := OnClick;
 Check.OnDragDrop := OnDragDrop;
 Check.OnDragOver := OnDragOver;
 For I := 1 to countfilt(item.parameters, '|') do begin
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'TRUE' then TrueStr := V;
  If uppercase(N) = 'FALSE' then FalseStr := V;
 end;
 SETVALUE(Item.Value);
 OnValuePanelResize(self);
 Modified := False;
end;

destructor TtcpItemCheck.Destroy;
begin
 FreeAndNil(Check);
 inherited;
end;

procedure TtcpItemCheck.Disable;
begin
 inherited;
 IsEnabled := False;
 Check.Enabled := False;
end;

procedure TtcpItemCheck.Enable;
begin
 inherited;
 IsEnabled := true;
 Check.Enabled := true;
end;

function TtcpItemCheck.GETVALUE: String;
begin
 If Check.Checked then Result := TrueStr
  else Result := FalseStr;
end;

procedure TtcpItemCheck.OnClick(Sender: TObject);
begin
 Modified := true;
end;

procedure TtcpItemCheck.OnEnter(Sender: TObject);
begin
 Modified := false;
 PaintSelected;
end;

procedure TtcpItemCheck.OnExit(Sender: TObject);
begin
 PaintUnSelected;
 SendUpdate;
end;

procedure TtcpItemCheck.OnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 LastKeyIsUp := False; LastKeyIsDown := False;
 Case Key of
  vk_Down: Begin
            Key := vk_add;
            LastKeyIsDown := True;
            Exit;
           end;
  vk_Up: Begin
          Key := vk_add;
          LastKeyIsUp := true;
          exit;
         end;
  vk_Return: Begin
              // Allow checking/unchecking using ENTER
              Check.Checked := Not Check.Checked;
             end;
 end;
end;

procedure TtcpItemCheck.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 If LastKeyIsUp then begin
  Key := vk_add;
  LastKeyIsUp := False;
  OwnerPanel.SelectPrevious(self);
 end;
 If LastKeyIsDown then begin
  Key := vk_add;
  LastKeyIsDown := false;
  OwnerPanel.SelectNext(self);
 end;
end;

procedure TtcpItemCheck.OnValuePanelResize(Sender: TObject);
begin
 inherited;
end;

procedure TtcpItemCheck.Select(AsNextInOrder: Boolean);
begin
 inherited;
// PaintSelected;
 If IsEnabled then begin
  Check.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemCheck.SendUpdate(Force: Boolean = False);
Var
 S, V: String;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 If Check.Checked then V := TrueStr
  else V := FalseStr;
 S := V;
 OwnerPanel.OI.UpdateParameter(ItemName, V, Item.Parameters);
 If S <> V then begin
  If uppercase(V) = uppercase(TrueStr) then Check.Checked := true
   else Check.Checked := false;
 end;
 Modified := false;
end;

procedure TtcpItemCheck.SETVALUE(const Value: String);
begin
 inherited;
 If uppercase(Value) = uppercase(trueStr) then Check.Checked := true
  else Check.Checked := false;
 Modified := True;
 SendUpdate;
end;

{ TtcpItemCalendar }

constructor TtcpItemCalendar.Create(aOwner: TComponent; ParentCont: TtcpOIPanel;
  Item: TtcpOIItem);
begin
 inherited;
 DateTimePicker := TDateTimePicker.Create(ValuePanel);
 DateTimePicker.Parent := ValuePanel;

// DateTimePicker.BorderStyle := bsNone;
 DateTimePicker.Top := 2;
 DateTimePicker.Left := 4;
 DateTimePicker.Height := 13;
 DateTimePicker.Color := OwnerPanel.OI.ItemBackground;
 DateTimePicker.Font.Color := OwnerPanel.OI.ItemFontColor;
 DateTimePicker.OnKeyPress := OnKeyPress;
 DateTimePicker.OnKeyUp := OnKeyUp;
 DateTimePicker.OnKeyDown := OnKeyDown;
 DateTimePicker.OnExit := OnExit;
 DateTimePicker.OnEnter := OnEnter;
 DateTimePicker.OnChange := OnChange;
 DateTimePicker.OnDragDrop := OnDragDrop;
 DateTimePicker.OnDragOver := OnDragOver;
 Modified := false;

 // Setting the value is normally done directly
 // but this object requires advanced alteration
 // hence it's easier to just call the SETVALUE method.
 SETVALUE(Item.Value);

 OnValuePanelResize(self);

end;

destructor TtcpItemCalendar.Destroy;
begin
  DateTimePicker.Free;
  inherited;
end;

procedure TtcpItemCalendar.Disable;
begin
 inherited;
 IsEnabled := False;
 DateTimePicker.Enabled := False;
end;

procedure TtcpItemCalendar.Enable;
begin
 inherited;
 IsEnabled := True;
 DateTimePicker.Enabled := True;
end;

function TtcpItemCalendar.GETVALUE: String;
Var
 Y, M, D: Word;
begin
 DecodeDate(DateTimePicker.Date, Y, M, D);
 Result := AddZero(IntToStr(D), 2) + AddZero(IntToStr(M), 2) + IntToStr(Y);
end;

procedure TtcpItemCalendar.OnChange(Sender: TObject);
begin
 Modified := true;
// SendUpdate;
end;

procedure TtcpItemCalendar.OnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 // TDateTimePicker changes content on KeyDown
 // Arrow keys Inc or Dec the selected value
 // Setting the Key value to 0 will avoid
 // the control changing the value.
 Case Key of
  VK_UP: Key := 0;
  VK_Down: Key := 0;
 End;
end;

procedure TtcpItemCalendar.OnKeyPress(Sender: TObject; var Key: Char);
begin
 If Key = #13 then begin
  Key := #0;
  SendUpdate;
 end;
end;

procedure TtcpItemCalendar.OnEnter(Sender: TObject);
begin
 PaintSelected;
end;

procedure TtcpItemCalendar.OnExit(Sender: TObject);
begin
 SendUpdate;
 PaintUnSelected;
end;

procedure TtcpItemCalendar.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 Case Key of
  VK_UP: Begin
          OwnerPanel.SelectPrevious(self);
          Key := 0;
         end;
  VK_DOWN: Begin
            OwnerPanel.SelectNext(self);
            Key := 0;
           end;
 end;
end;

procedure TtcpItemCalendar.OnValuePanelResize(Sender: TObject);
begin
 DateTimePicker.Width := ValuePanel.Width - 4;
end;

procedure TtcpItemCalendar.Select(AsNextInOrder: Boolean);
begin
 If IsEnabled then begin
  DateTimePicker.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemCalendar.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
 Y, M, D: Word;
 Date: TDate;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 N := ItemName;
 DecodeDate(DateTimePicker.Date, Y, M, D);
 V := AddZero(IntToStr(D), 2) + AddZero(IntToStr(M), 2) + IntToStr(Y);
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 Date := EncodeDate(Y, M, D);
 If Date <> DateTimePicker.Date then DateTimePicker.Date := Date;
 Modified := false; 
end;

procedure TtcpItemCalendar.SETVALUE(const Value: String);
Var
 YS, MS, DS: String;
begin
 YS := '1980'; MS := '12'; DS := '25'; // Initialize Date as my Birthdate - Should never happen ...
(*
 If Length(Value) = 6 then begin
  // Format is DDMMYY
  DS := Copy(Value, 1, 2);
  MS := Copy(Value, 3, 2);
  YS := Copy(Value, 5, 2);
 end;
*)
 If Length(Value) = 8 then begin
  // Format is DDMMYYYY
  DS := Copy(Value, 1, 2);
  MS := Copy(Value, 3, 2);
  YS := Copy(Value, 5, 4);
 end;
 If (YS = '1980') and (MS = '12') and (DS = '25') then begin
  // Date was invalid, default to todays date.
  DateTimePicker.Date := Now;
 end
  else
 begin
  // Date is valid. Contruct from Strings
  DateTimePicker.Date := EncodeDate(StrToInt(YS), StrToInt(MS), StrToInt(DS));
 End;
 Modified := True;
 SendUpdate;
end;

{ TtcpItemNumeric }

constructor TtcpItemNumeric.Create(aOwner: TComponent; ParentCont: TtcpOIPanel;
  Item: TtcpOIItem);
Var
 I: Integer;
 N, V: String;
begin
 Inherited;
 Edit := TEdit.Create(ValuePanel);
 Edit.Parent := ValuePanel;
 Edit.Text := Item.Value;
 Edit.BorderStyle := bsNone;
 Edit.Top := 2;
 Edit.Left := 4;
 Edit.Height := 13;
 Edit.AutoSelect := true;
 Edit.Color := OwnerPanel.OI.ItemBackground;
 Edit.Font.Color := OwnerPanel.OI.ItemFontColor;
 OnValuePanelResize(self);
 Edit.OnKeyPress := OnKeyPress;
 Edit.OnKeyUp := OnKeyUp;
 Edit.OnExit := OnEditExit;
 Edit.OnEnter := OnEditEnter;
 Edit.OnChange := OnChange;
 Edit.OnDragDrop := OnDragDrop;
 Edit.OnDragOver := OnDragOver;
 For I := 1 to countfilt(item.parameters, '|') do begin
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If (uppercase(N) = 'SEP') or (Uppercase(N) = 'SEPERATOR') then Sep := V[1];
  If uppercase(N) = 'DECIMAL' then begin
   Decimal := StrToInt(V);
  end;
  If Uppercase(N) = 'MAXCHAR' then begin
   Try
    Edit.MaxLength := StrToInt(V);
   Except
    Edit.MaxLength := 0;
   End;
  end;
 end;
 Modified := false;
end;

destructor TtcpItemNumeric.Destroy;
begin
  Edit.Free;
  inherited;
end;

procedure TtcpItemNumeric.Disable;
begin
 inherited;
 IsEnabled := False;
 Edit.Enabled := False;
end;

procedure TtcpItemNumeric.Enable;
begin
 inherited;
 IsEnabled := True;
 Edit.Enabled := True;
end;

function TtcpItemNumeric.GETVALUE: String;
begin
 Result := Edit.Text;
end;

procedure TtcpItemNumeric.OnChange(Sender: TObject);
begin
 Modified := true;
end;

procedure TtcpItemNumeric.OnEditEnter(Sender: TObject);
begin
 PaintSelected;
end;

procedure TtcpItemNumeric.OnEditExit(Sender: TObject);
begin
 SendUpdate;
 PaintUnSelected;
end;

procedure TtcpItemNumeric.OnKeyPress(Sender: TObject; var Key: Char);
Var
 AllowUpdate: Boolean;
 Str: String;
begin
 AllowUpdate := false;

 Case Ord(Key) of
  Ord('0') .. Ord('9'): Begin
                         Str := Edit.Text;
                         If (CountFilt(Str, Sep) = 2) and (not (Edit.SelLength = Length(Edit.Text))) then begin
                          If Edit.SelStart = Length(Str) then begin
                           If Length(Getfilt(Str, 2, Sep)) < Decimal then begin
                            Edit.SelText := Key;
                            AllowUpdate := true;
                           end;
                          End;
                         end
                          else
                         begin
                          Edit.SelText := Key;
                          AllowUpdate := true;
                         end;
                        End;
  8: Begin
      // Backspace
      // Pass it back to the Control by exiting Procedure
      Exit;
     End;
  13: Begin
       // Enter
       AllowUpdate := true;
      End;
 End;

 // For some reason can't use variables in the CASE statement, so using this IF
 // outside it.
 If Ord(Key) = ord(Sep) then Begin
  if (CountFilt(Edit.Text, Sep) < 2) and (Not (Decimal = 0)) then begin
   // Don't allow multiple decimal operators
   Edit.SelText := Key;
   AllowUpdate := true;
  End;
 end;


 If AllowUpdate then begin
  Key := #0;
  Modified := true;
 end
  else
 Begin
  Key := #0;
//  Beep(25000, 500); // it will not allow the standard non-parameter function which
                    // will play "Critical Stop" in this unit, don't really know why 
 End;
end;

procedure TtcpItemNumeric.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 Case Key of
  VK_UP: Begin
          OwnerPanel.SelectPrevious(self);
         end;
  VK_DOWN: Begin
            OwnerPanel.SelectNext(self);
           end;
 end;
end;

procedure TtcpItemNumeric.OnValuePanelResize(Sender: TObject);
begin
 Edit.Width := ValuePanel.Width - 4;
end;

procedure TtcpItemNumeric.Select(AsNextInOrder: Boolean);
begin
 If IsEnabled then begin
  Edit.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemNumeric.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 N := ItemName;
 V := Edit.TExt;
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 If V <> Edit.Text then Edit.TExt := V;
 Modified := false;
end;

procedure TtcpItemNumeric.SETVALUE(const Value: String);
begin
 Edit.Text := Value;
 Modified := True;
 SendUpdate;
end;

{ TtcpItemMemo }

function TtcpItemMemo.RichCol: Longint;
begin
 Result := Memo.SelStart - SendMessage(Memo.Handle, EM_LINEINDEX, SendMessage(Memo.Handle, EM_LINEFROMCHAR, Memo.SelStart, 0), 0);
end;

function TtcpItemMemo.RichRow: Longint;
begin
 Result := SendMessage(Memo.Handle, EM_LINEFROMCHAR, Memo.SelStart, 0);
end;

Function TtcpItemMemo.GetLine: Longint;
Begin
 Result := SendMessage(Memo.Handle, EM_LINEFROMCHAR, -1, 0);
end;

constructor TtcpItemMemo.Create(aOwner: TComponent; ParentCont: TtcpOIPanel;
  Item: TtcpOIItem);
Var
 I: Integer;
 N, V: String;
begin
 Inherited;
 Height := 80;
 Memo := TMemo.Create(ValuePanel);
 Memo.Parent := ValuePanel;
 Memo.Lines.Text := Item.Value;
 Memo.BorderStyle := bsNone;
 Memo.Top := 2;
 Memo.Left := 4;
 Memo.Height := 75;
// Memo.AutoSelect := true;
 Memo.Color := OwnerPanel.OI.ItemBackground;
 Memo.Font.Color := OwnerPanel.OI.ItemFontColor;
 OnValuePanelResize(self);
 Memo.OnKeyPress := OnKeyPress;
 Memo.OnKeyDown := OnKeyDown;
 Memo.OnKeyUp := OnKeyUp;
 Memo.OnExit := OnExit;
 Memo.OnEnter := OnEnter;
 Memo.OnDragDrop := OnDragDrop;
 Memo.OnDragOver := OnDragOver;
 MaxRow := 0;
 MaxChar := 0;
 For I := 1 to countfilt(item.parameters, '|') do begin
  N := GetFilt(Item.Parameters, I, '|');
  V := GetFilt(N, 2, '=');
  N := GetFilt(N, 1, '=');
  If uppercase(N) = 'MAXLINES' then begin
   Try
    MaxRow := StrToInt(V);
   Except
    MaxRow := 0;
   End;
  end;
  If Uppercase(N) = 'MAXCHAR' then begin
   Try
    MaxChar := StrToInt(V);
   Except
    MaxChar := 0;
   End;
  end;
  If Uppercase(N) = 'ILLEGAL' then begin
   Illegal := V;
  end;
 end;

 Memo.WordWrap := False;

 If (MaxChar > 0) or (MaxRow > 0) Then begin
  // Ensure that if Either limiter is set, then ensure that wordwrap isn't active,
  // since it will limit lines and rows based on the size of the control.
  Memo.WordWrap := False;
 end;

 Modified := false;

end;

destructor TtcpItemMemo.Destroy;
begin
 Memo.Free;
 inherited;
end;

procedure TtcpItemMemo.Disable;
begin
 inherited;
 IsEnabled := False;
 Memo.Enabled := False;
end;

procedure TtcpItemMemo.Enable;
begin
 inherited;
 IsEnabled := True;
 Memo.Enabled := True;
end;

function TtcpItemMemo.GETVALUE: String;
begin
 Result := Memo.Lines.Text;
end;

procedure TtcpItemMemo.OnEnter(Sender: TObject);
begin
 PaintSelected;
end;

procedure TtcpItemMemo.OnExit(Sender: TObject);
begin
 PaintUnSelected;
 SendUpdate;
end;

procedure TtcpItemMemo.OnKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
Var
 CurrentRow: Integer;
 Str: String;
begin
 LastKeyIsUp := False; LastKeyIsDown := False;
 Case Key of
  VK_Up: Begin
          If (GetLine = 0) and (Shift = []) then begin
           Key := VK_Add;
           LastKeyIsUp := True;
          end;
         end;
  VK_Down: Begin
            If (GetLine >= Memo.Lines.Count - 1) and (Shift = []) then begin
             Key := VK_Add;
             LastKeyIsDown := True;
            end;
           End;
  VK_Delete: Begin
              // Don't hinder Delete if there is no limitations to Line Char Length.
              If MaxChar = 0 then exit;

//              Make sure that delete cannot be performed is the line that is about
//              to be pulled merge with the current line, doesn't make the total line
//              length bigger than MAXCHAR.
//              use VK_Rshift, to change the KEY and suppress the action.

              // is there a line after the current?
              CurrentRow := RichRow;
              If (Memo.Lines.Count - 1) > CurrentRow then begin

               Str := Memo.Lines[CurrentRow] + Memo.Lines[CurrentRow + 1];

               // if the content of next line + current line > MAXCHAR
              If Length(Str) > MAXCHAR then begin
                // Cancel KeyPress
                Key := VK_RShift;
               End;
              end;
             end;
 End;
end;

procedure TtcpItemMemo.OnKeyPress(Sender: TObject; var Key: Char);
Var
 I: Integer;
 Str, ActiveLine: String;
begin
 If (Key <> #13) Then begin

  // Don't process backspace
  If Key = #8 then exit;
  
  // Check for illegal
  For I := 1 to length(Illegal) do begin
   If Illegal[I] = Key then begin
    Key := #0;
    exit;
   end;
  end;

  // If MaxChar = 0 be sure to remove limiter
  If (RichCol >= MaxChar) And Not (MaxChar = 0) then begin
   // if the Row limit is in effect cancel entry
   If Memo.Lines.Count = MaxRow then begin
    Key := #0;
    Exit;
   End
    else
   Begin
    ActiveLine := Memo.Lines[RichRow];
    Str := GetFilt(ActiveLine, CountFilt(ActiveLine, ' '), ' ');
    If CountFilt(Activeline, ' ') > 1 Then Begin
     ActiveLine := DelLastFilt(ActiveLine, ' ');
     Memo.Lines[RichRow] := ActiveLine;
     Memo.SelText := #13#10 + Str;
    end
     else
    begin
     Memo.SelText := #13#10;
    End;
   End;
  End
   else
  If (RichRow >= MaxRow) and not (MaxRow = 0) then Begin
   Key:= #0;
   Exit;
  End;
  Memo.SelText := Key;
  Key := #0;
  Modified := true;
  // Exit because we don't want to run update twice.
  Exit;
 end;
 If Key = #13 then begin
  // don't accept the enter, if we're on the last line.
  If Memo.Lines.Count = (MaxRow) then begin
   Key := #0;
   Exit;
  end;
  Modified := true;
  SendUpdate;
 end;

end;

procedure TtcpItemMemo.OnKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 If LastKeyIsUp then begin
  Key := vk_add;
  LastKeyIsUp := False;
  OwnerPanel.SelectPrevious(self);
 end;
 If LastKeyIsDown then begin
  Key := vk_add;
  LastKeyIsDown := false;
  OwnerPanel.SelectNext(self);
 end;
end;

procedure TtcpItemMemo.OnValuePanelResize(Sender: TObject);
begin
 Memo.Width := ValuePanel.Width - 4;
end;

procedure TtcpItemMemo.Select(AsNextInOrder: Boolean);
begin
 If IsEnabled then begin
  Memo.SetFocus;
  // exit routine since the remainder is just to bypass if not enabled
  Exit;
 end;
 If AsNextInOrder then begin
  OwnerPanel.SelectNext(Self);
 end
  else
 begin
  OwnerPanel.SelectPrevious(Self);
 end;
end;

procedure TtcpItemMemo.SendUpdate(Force: Boolean = False);
Var
 N, V: String;
begin
 // if it hasn't been modified don't update, unless Force update is required.
 If (not modified) and (not Force) then exit;
 N := ItemName;
 V := Memo.Lines.Text;
 OwnerPanel.OI.UpdateParameter(N, V, Item.Parameters);
 If V <> Memo.Lines.Text then Memo.Lines.Text := V;
 Modified := false;
end;

procedure TtcpItemMemo.SETVALUE(const Value: String);
begin
 Memo.Lines.Text := Value;
 Modified := True;
 SendUpdate;
end;

{

}
end.
