unit KsItems;

{$I KsCommonVer.inc}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, CommCtrl, Menus, ActnList, ImgList, KsBitmap, KsUtils, KsEffects;

const
  ItemStep      = 20;
  GlyphWidth    = 22;
  SubMenuWidth  = 16;
  SepHeight     = 15;

  WM_CLICKEVENT = WM_USER + 1210; { Need for handle OnClick event }

type

  TKsCustomItem = class;

  TKsCustomItemActionLink = class(TActionLink)
  protected
    FClient: TKsCustomItem;
    procedure AssignClient (AClient: TObject); override;
    function IsCaptionLinked: Boolean; override;
    function IsCheckedLinked: Boolean; override;
    function IsEnabledLinked: Boolean; override;
    function IsHelpContextLinked: Boolean; override;
    function IsHintLinked: Boolean; override;
    function IsImageIndexLinked: Boolean; override;
    function IsShortCutLinked: Boolean; override;
    function IsVisibleLinked: Boolean; override;
    function IsOnExecuteLinked: Boolean; override;
    procedure SetCaption (const Value: String); override;
    procedure SetChecked (Value: Boolean); override;
    procedure SetEnabled (Value: Boolean); override;
    procedure SetHelpContext (Value: THelpContext); override;
    procedure SetHint (const Value: String); override;
    procedure SetImageIndex (Value: Integer); override;
    procedure SetShortCut (Value: TShortCut); override;
    procedure SetVisible (Value: Boolean); override;
    procedure SetOnExecute (Value: TNotifyEvent); override;
  end;

  TKsCustomItemActionLinkClass = class of TKsCustomItemActionLink;

  TKsItemView = class;

  TKsMDIItemKind = (mikNone, mikClose, mikRestore, mikMinimize, mikSysMenu);

{ TKsCustomItem }

  TKsCustomItem = class(TComponent)
  private
    FCaption: String;
    FChecked: Boolean;
    FEnabled: Boolean;
    FHelpContext: THelpContext;
    FHint: String;
    FImageIndex: Integer;
    FImages: TCustomImageList;
    FShortCut: TShortCut;
    FVisible: Boolean;
    FOnClick: TNotifyEvent;

    FActionLink: TKsCustomItemActionLink;
    FParent: TKsCustomItem;
    FParentComponent: TComponent;
    FItems: TList;
    FPopupBlend: boolean;
    FPopupBlendValue: integer;
    FOnInternalChange: TNotifyEvent;
    FMDIItemKind: TKsMDIItemKind;
    FActiveMDIForm: TComponent;
    FDropShadow: boolean;
    FShowAnimationTime: integer;
    FShadowWidth: integer;
    FShowAnimation: TKsAnimation;
    FFirst: boolean;
    FOnlyFirst: boolean;
    FOwnerForm: TWinControl;
    FBiDiMode: TBiDiMode;
    FIsToolbar: boolean;
    FTray: boolean;

    function GetAction: TBasicAction;
    procedure SetAction (Value: TBasicAction);
    procedure SetCaption (Value: String);
    procedure SetChecked (Value: Boolean);
    procedure SetEnabled (Value: Boolean);
    procedure SetImageIndex (Value: Integer);
    procedure SetImages (Value: TCustomImageList);
    procedure SetVisible (Value: Boolean);
    function GetItemCount: integer;
    function GetItem(Index: Integer): TKsCustomItem;
    function GetOwnerCustomForm: TComponent;

    procedure DoActionChange(Sender: TObject);
    function IsCaptionStored: Boolean;
    function IsCheckedStored: Boolean;
    function IsEnabledStored: Boolean;
    function IsHelpContextStored: Boolean;
    function IsHintStored: Boolean;
    function IsImageIndexStored: Boolean;
    function IsOnClickStored: Boolean;
    function IsShortCutStored: Boolean;
    function IsVisibleStored: Boolean;

    procedure ActionChange(Sender: TObject; CheckDefaults: Boolean);
    function GetImgList: TCustomImageList;
    procedure SetPopupBlend(const Value: boolean);
    procedure SetPopupBlendValue(const Value: integer);
    procedure SetActiveMDIForm(const Value: TComponent);
    procedure SetDropShadow(const Value: boolean);
    procedure SetShadowWidth(const Value: integer);
    procedure SetShowAnimation(const Value: TKsAnimation);
    procedure SetShowAnimationTime(const Value: integer);
    procedure SetFirst(const Value: boolean);
    procedure SetOnlyFirst(const Value: boolean);
    procedure SetOwnerForm(const Value: TWinControl);
    function UseRightToLeftAlignment: Boolean;
    function UseRightToLeftReading: Boolean;
    procedure SetBiDiMode(const Value: TBiDiMode);
    procedure SetIsToolbar(const Value: boolean);
    procedure SetTray(const Value: boolean);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure SetParentComponent(Value: TComponent); override;

    function DrawTextBiDiModeFlags(Flags: Integer): Longint;
    function DrawTextBiDiModeFlagsReadingOnly: Longint;

    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual;

    procedure DoClick; dynamic;

    class function GetPopupWindowClass: TClass; virtual;
    property ActionLink: TKsCustomItemActionLink read FActionLink write FActionLink;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;

    function HasParent: Boolean; override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    function GetParentComponent: TComponent; override;

    function FindItem(Value: Integer; Kind: TFindItemKind): TKsCustomItem;
    function FindItemByChar(C: Char): TKsCustomItem;
    function IsShortCut(var Msg: TWMKey): boolean;

    procedure Change; virtual;

    procedure Popup(View: TKsItemView; X, Y: integer; Rect: TRect);

    procedure CalcSize(Buffer: TKsBitmap; View: TKsItemView;
      var AWidth, AHeight: integer); virtual;
    procedure DrawItem(Buffer: TKsBitmap; View: TKsItemView; Rect: TRect;
      Active, Hover: boolean); virtual;

    procedure Clear;
    procedure Add(AItem: TKsCustomItem);
    function ContainsItem(AItem: TKsCustomItem): Boolean;
    procedure Delete(Index: Integer);
    function IndexOf(AItem: TComponent): Integer;
    procedure Insert(NewIndex: Integer; AItem: TKsCustomItem);
    procedure Remove(Item: TKsCustomItem);
    procedure InitiateAction; virtual;
    { MenuBar Animation }
    property First: boolean read FFirst write SetFirst;
    property OnlyFirst: boolean read FOnlyFirst write SetOnlyFirst;
    { Animation and Shadow }
    property DropShadow: boolean read FDropShadow write SetDropShadow;
    property ShadowWidth: integer read FShadowWidth write SetShadowWidth;
    property ShowAnimation: TKsAnimation read FShowAnimation write SetShowAnimation;
    property ShowAnimationTime: integer read FShowAnimationTime write SetShowAnimationTime;
    { Intenal properies }
    property ActiveMDIForm: TComponent read FActiveMDIForm write SetActiveMDIForm;
    property BiDiMode: TBiDiMode read FBiDiMode write SetBiDiMode;
    property IsToolbar: boolean read FIsToolbar write SetIsToolbar;
    property MDIItemKind: TKsMDIItemKind read FMDIItemKind write FMDIItemKind;
    property ImgList: TCustomImageList read GetImgList;
    property Items[Index: Integer]: TKsCustomItem read GetItem; default;
    property OwnerForm: TWinControl read FOwnerForm write SetOwnerForm;
    property Tray: boolean read FTray write SetTray;
    property OnInternalChange: TNotifyEvent read FOnInternalChange write FOnInternalChange stored false;
  published
    { Properties }
    property Action: TBasicAction read GetAction write SetAction;
    property Count: Integer read GetItemCount;
    property HelpContext: THelpContext read FHelpContext write FHelpContext stored IsHelpContextStored default 0;
    property Hint: String read FHint write FHint stored IsHintStored;
    property Parent: TKsCustomItem read FParent;
    property ParentComponent: TComponent read FParentComponent write FParentComponent;
    property PopupBlend: boolean read FPopupBlend write SetPopupBlend;
    property PopupBlendValue: integer read FPopupBlendValue write SetPopupBlendValue;
    property Checked: Boolean read FChecked write SetChecked stored IsCheckedStored default False;
    property Enabled: Boolean read FEnabled write SetEnabled stored IsEnabledStored default True;
    property Caption: String read FCaption write SetCaption stored IsCaptionStored;
    property ImageIndex: Integer read FImageIndex write SetImageIndex stored IsImageIndexStored default -1;
    property Images: TCustomImageList read FImages write SetImages;
    property ShortCut: TShortCut read FShortCut write FShortCut stored IsShortCutStored default 0;
    property Visible: Boolean read FVisible write SetVisible stored IsVisibleStored default True;
    property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
  end;

  TKsCustomItemClass = class of TKsCustomItem;

{ TKsItemView class }

  TKsViewOrientation = (voVertical, voHorizontal);

  TKsItemView = class(TComponent)
  private
    FItems: TKsCustomItem;
    FIsMenuBar: boolean;
    FOrientation: TKsViewOrientation;
    FParentComponent: TComponent;
    FWindow: TWinControl;
    FSelItem: TKsCustomItem;
    FDropDown: boolean;
    FLoop: boolean;

    FParentView: TKsItemView;
    FChildView: TKsItemView;

    FBuffer: TKsBitmap;
    FLeft: integer;
    FTop: integer;

  protected
    procedure SetSize(ASize: TPoint); virtual;
    procedure SetPosition(APosition: TPoint); virtual;
    procedure Invalidate; virtual;
    function CheckDropDown: Boolean;

    function Popup: Boolean;
    procedure SelectNext;
    procedure SelectPrev;
  public
    constructor CreateView(AOwner: TComponent; AIsMenuBar: boolean;
      AOrientation: TKsViewOrientation); 
    destructor Destroy; override;

    procedure CalcSize;
    function MessageLoop: Boolean;

    procedure Paint(Buffer: TKsBitmap); virtual;
    { Mouse routines }
    function GetItemRect(Item: TKsCustomItem): TRect;
    procedure UpdateHover(P: TPoint);
    { Properties }
    property Buffer: TKsBitmap read FBuffer write FBuffer;
    property Items: TKsCustomItem read FItems write FItems;
    property IsMenuBar: boolean read FIsMenuBar;
    property ParentComponent: TComponent read FParentComponent write FParentComponent;
    property ParentView: TKsItemView read FParentView write FParentView;
    property Window: TWinControl read FWindow write FWindow;
    property Left: integer read FLeft write FLeft;
    property Top: integer read FTop write FTop;
  end;

  EKsItemError = class(Exception);

{ TKsItemContainer abstract items container }

  TKsInsertItemProc = procedure(AParent: TComponent; AItem: TKsCustomItem) of object;
  TKsGetItemClassProc = function: TKsCustomItemClass of object;

procedure RegisterContainerClass(const AClass: TClass; AInsertItemProc: TKsInsertItemProc;
  AGetItemClassProc: TKsGetItemClassProc);
procedure UnregisterContainerClass(const AClass: TClass);
function GetItemClass(const AClass: TClass): TKsCustomItemClass;

var
  MenuFont: TFont;

implementation {===============================================================}

uses KsForms, KsCommonConst, KsMenus, KsMenuForm;

var
  ContainerClassList: TList;
  LastPos: TPoint;
  ClickHandler: TForm;

type

  PContainerClassData = ^TContainerClassData;
  TContainerClassData = record
    Cls: TClass;
    InsertItemProc: TKsInsertItemProc;
    GetItemClassProc: TKsGetItemClassProc;
    RefCount: Integer;
  end;

procedure InitMenuFont;
var
  NonClientMetrics: TNonClientMetrics;
begin
  MenuFont := TFont.Create;
  NonClientMetrics.cbSize := SizeOf(NonClientMetrics);
  if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0) then
    MenuFont.Handle := CreateFontIndirect(NonClientMetrics.lfMenuFont);
end;

procedure RegisterContainerClass(const AClass: TClass; AInsertItemProc: TKsInsertItemProc;
  AGetItemClassProc: TKsGetItemClassProc);
var
  i: Integer;
  Data: PContainerClassData;
begin
  if ContainerClassList = nil then ContainerClassList := TList.Create;
  for i := 0 to ContainerClassList.Count-1 do
    with PContainerClassData(ContainerClassList[I])^ do
      if Cls = AClass then
      begin
        Inc (RefCount);
        InsertItemProc := AInsertItemProc;
        GetItemClassProc := AGetItemClassProc;
        Exit;
      end;
  ContainerClassList.Expand;
  New (Data);
  Data.Cls := AClass;
  Data.RefCount := 1;
  Data.InsertItemProc := AInsertItemProc;
  Data.GetItemClassProc := AGetItemClassProc;
  ContainerClassList.Add (Data);
end;

procedure UnregisterContainerClass(const AClass: TClass);
var
  i: Integer;
  Data: PContainerClassData;
begin
  if Assigned(ContainerClassList) then
    for i := 0 to ContainerClassList.Count-1 do
    begin
      Data := ContainerClassList[I];
      if Data.Cls = AClass then
      begin
        Dec (Data.RefCount);
        if Data.RefCount = 0 then
        begin
          ContainerClassList.Delete (I);
          Dispose (Data);
          if ContainerClassList.Count = 0 then
          begin
            ContainerClassList.Free;
            ContainerClassList := nil;
          end;
        end;
        Break;
      end;
    end;
end;

function GetItemClass(const AClass: TClass): TKsCustomItemClass;
var
  i: integer;
begin
  Result := nil;
  if ContainerClassList = nil then Exit;

  for i := 0 to ContainerClassList.Count-1 do
    with PContainerClassData(ContainerClassList[I])^ do
      if Cls = AClass then
      begin
        Result := GetItemClassProc;
        Exit;
      end;
end;

{ Click message handler =======================================================}

type

  TKsClickHandler = class(TForm)
  private
    procedure WMClickEvent(var Msg: TMessage); message WM_CLICKEVENT;
  public
    constructor CreateHandler;
  end;

{ TKsClickHandler }

constructor TKsClickHandler.CreateHandler;
begin
  inherited CreateNew(nil);
  SetBounds(10000, 10000, 0, 0);
end;

procedure TKsClickHandler.WMClickEvent(var Msg: TMessage);
var
  Item: TKsCustomItem;
begin
  if Msg.LParam = 0 then Exit;
  if Pointer(Msg.LParam) = nil then Exit;
  if not (TObject(Msg.LParam) is TKsCustomItem) then Exit;

  Application.ProcessMessages;
  
  Item := TKsCustomItem(Msg.LParam);

  with Item do
  begin
    if Assigned(OnClick) and (Action <> nil) and not (@FOnClick = @Action.OnExecute) then
      FOnClick(Item)
    else
      if not (csDesigning in ComponentState) and (ActionLink <> nil) then
        ActionLink.Execute
      else
        if Assigned(FOnClick) then
          FOnClick(Item);
  end;
end;

{ TKsCustomItemActionLink =====================================================}

procedure TKsCustomItemActionLink.AssignClient (AClient: TObject);
begin
  FClient := AClient as TKsCustomItem;
end;

function TKsCustomItemActionLink.IsCaptionLinked: Boolean;
begin
  Result := inherited IsCaptionLinked and
    (FClient.Caption = (Action as TCustomAction).Caption);
end;

function TKsCustomItemActionLink.IsCheckedLinked: Boolean;
begin
  Result := inherited IsCheckedLinked and
    (FClient.Checked = (Action as TCustomAction).Checked);
end;

function TKsCustomItemActionLink.IsEnabledLinked: Boolean;
begin
  Result := inherited IsEnabledLinked and
    (FClient.Enabled = (Action as TCustomAction).Enabled);
end;

function TKsCustomItemActionLink.IsHelpContextLinked: Boolean;
begin
  Result := inherited IsHelpContextLinked and
    (FClient.HelpContext = (Action as TCustomAction).HelpContext);
end;

function TKsCustomItemActionLink.IsHintLinked: Boolean;
begin
  Result := inherited IsHintLinked and
    (FClient.Hint = (Action as TCustomAction).Hint);
end;

function TKsCustomItemActionLink.IsImageIndexLinked: Boolean;
begin
  Result := inherited IsImageIndexLinked and
    (FClient.ImageIndex = (Action as TCustomAction).ImageIndex);
end;

function TKsCustomItemActionLink.IsShortCutLinked: Boolean;
begin
  Result := inherited IsShortCutLinked and
    (FClient.ShortCut = (Action as TCustomAction).ShortCut);
end;

function TKsCustomItemActionLink.IsVisibleLinked: Boolean;
begin
  Result := inherited IsVisibleLinked and
    (FClient.Visible = (Action as TCustomAction).Visible);
end;

function TKsCustomItemActionLink.IsOnExecuteLinked: Boolean;
begin
  Result := inherited IsOnExecuteLinked and
    (@FClient.OnClick = @Action.OnExecute);
end;

procedure TKsCustomItemActionLink.SetCaption (const Value: string);
begin
  if IsCaptionLinked then FClient.Caption := Value;
end;

procedure TKsCustomItemActionLink.SetChecked (Value: Boolean);
begin
  if IsCheckedLinked then FClient.Checked := Value;
end;

procedure TKsCustomItemActionLink.SetEnabled (Value: Boolean);
begin
  if IsEnabledLinked then FClient.Enabled := Value;
end;

procedure TKsCustomItemActionLink.SetHelpContext (Value: THelpContext);
begin
  if IsHelpContextLinked then FClient.HelpContext := Value;
end;

procedure TKsCustomItemActionLink.SetHint (const Value: string);
begin
  if IsHintLinked then FClient.Hint := Value;
end;

procedure TKsCustomItemActionLink.SetImageIndex (Value: Integer);
begin
  if IsImageIndexLinked then FClient.ImageIndex := Value;
end;

procedure TKsCustomItemActionLink.SetShortCut(Value: TShortCut);
begin
  if IsShortCutLinked then FClient.ShortCut := Value;
end;

procedure TKsCustomItemActionLink.SetVisible(Value: Boolean);
begin
  if IsVisibleLinked then FClient.Visible := Value;
end;

procedure TKsCustomItemActionLink.SetOnExecute(Value: TNotifyEvent);
begin
  if IsOnExecuteLinked then FClient.OnClick := Value;
end;

{ TKsCustomItem }

constructor TKsCustomItem.Create(AOwner: TComponent);
begin
  inherited;
  FEnabled := True;
  FImageIndex := -1;
  FVisible := True;
  FItems := TList.Create;
  FPopupBlend := false;
  FPopupBlendValue := 150;
end;

destructor TKsCustomItem.Destroy;
var
  I: Integer;
begin
  for I := Count-1 downto 0 do 
    Items[I].Free;
  if Assigned(FParent) then
    FParent.Remove(Self);

  FItems.Free;

  FActionLink.Free;
  FActionLink := nil;

  inherited;
end;

function TKsCustomItem.IsCaptionStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsCaptionLinked;
end;

function TKsCustomItem.IsCheckedStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsCheckedLinked;
end;

function TKsCustomItem.IsEnabledStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsEnabledLinked;
end;

function TKsCustomItem.IsHintStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsHintLinked;
end;

function TKsCustomItem.IsHelpContextStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsHelpContextLinked;
end;

function TKsCustomItem.IsImageIndexStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsImageIndexLinked;
end;

function TKsCustomItem.IsShortCutStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsShortCutLinked;
end;

function TKsCustomItem.IsVisibleStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsVisibleLinked;
end;

function TKsCustomItem.IsOnClickStored: Boolean;
begin
  Result := (ActionLink = nil) or not FActionLink.IsOnExecuteLinked;
end;

function TKsCustomItem.GetAction: TBasicAction;
begin
  if FActionLink <> nil then
    Result := FActionLink.Action
  else
    Result := nil;
end;

procedure TKsCustomItem.DoActionChange(Sender: TObject);
begin
  if Sender = Action then ActionChange(Sender, False);
end;

procedure TKsCustomItem.ActionChange(Sender: TObject; CheckDefaults: Boolean);
begin
  if Action is TCustomAction then
    with TCustomAction(Sender) do
    begin
      if not CheckDefaults or (Self.Caption = '') then
        Self.Caption := Caption;
      if not CheckDefaults or (Self.Checked = False) then
        Self.Checked := Checked;
      if not CheckDefaults or (Self.Enabled = True) then
        Self.Enabled := Enabled;
      if not CheckDefaults or (Self.HelpContext = 0) then
        Self.HelpContext := HelpContext;
      if not CheckDefaults or (Self.Hint = '') then
        Self.Hint := Hint;
      if not CheckDefaults or (Self.ImageIndex = -1) then
        Self.ImageIndex := ImageIndex;
      if not CheckDefaults or (Self.ShortCut = scNone) then
        Self.ShortCut := ShortCut;
      if not CheckDefaults or (Self.Visible = True) then
        Self.Visible := Visible;
      if not CheckDefaults or not Assigned(Self.OnClick) then
        Self.OnClick := OnExecute;
    end;
end;

procedure TKsCustomItem.SetAction(Value: TBasicAction);
begin
  if Value = nil then begin
    FActionLink.Free;
    FActionLink := nil;
  end
  else begin
    if FActionLink = nil then
      FActionLink := TKsCustomItemActionLink.Create(Self);
    FActionLink.Action := Value;
    FActionLink.OnChange := DoActionChange;
    ActionChange(Value, csLoading in Value.ComponentState);
    Value.FreeNotification(Self);
  end;
end;

procedure TKsCustomItem.InitiateAction;
begin
  if FActionLink <> nil then FActionLink.Update;
end;

procedure TKsCustomItem.Loaded;
begin
  inherited;
  if Action <> nil then ActionChange(Action, True);
end;

procedure TKsCustomItem.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  I: Integer;
begin
  for I := 0 to FItems.Count-1 do
    Proc(FItems[I]);
end;

function TKsCustomItem.HasParent: Boolean;
begin
  Result := True;
end;

function TKsCustomItem.GetParentComponent: TComponent;
begin
  if (FParent <> nil) and (FParent.FParentComponent <> nil) then
    Result := FParent.FParentComponent
  else
    Result := FParent;
end;

procedure TKsCustomItem.SetParentComponent(Value: TComponent);
var
  i: integer;
begin
  if FParent <> nil then FParent.Remove(Self);

  if Value <> nil then
  begin
    if Value is TKsCustomItem then
      TKsCustomItem(Value).Add(Self)
    else
    begin
      if Assigned(ContainerClassList) then
        for i := ContainerClassList.Count-1 downto 0 do
          with PContainerClassData(ContainerClassList[I])^ do
            if Value is Cls then begin
              InsertItemProc(Value, Self);
              Break;
            end;
    end;
  end;
end;

procedure TKsCustomItem.Notification (AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if Operation = opRemove then
  begin
    if AComponent = Action then Action := nil;

    if AComponent = Images then Images := nil;
  end;
end;

procedure TKsCustomItem.Change;
var
  Item: TKsCustomItem;
begin
  if (csLoading in ComponentState) or (csDestroying in ComponentState) then Exit; 

  if Assigned(FOnInternalChange) then
    FOnInternalChange(Self);

  Item := Self;
  while Item.Parent <> nil do
    Item := Item.Parent;

  if Item <> nil then
    if Assigned(Item.FOnInternalChange) then
      Item.FOnInternalChange(Item);
end;

procedure TKsCustomItem.DoClick;
begin
  if Caption = '-' then Exit;
  if Count > 0 then Exit;
  if not Enabled then Exit;

  if ClickHandler <> nil then
  begin
    PostMessage(ClickHandler.Handle, WM_CLICKEVENT, 0, Integer(Self));
  end
  else
  begin
    if Assigned(FOnClick) and (Action <> nil) and not (@FOnClick = @Action.OnExecute) then
      FOnClick(Self)
    else
      if not (csDesigning in ComponentState) and (ActionLink <> nil) then
        ActionLink.Execute
      else
        if Assigned(FOnClick) then
          FOnClick(Self);
  end;
end;

function TKsCustomItem.GetItemCount: integer;
begin
  Result := FItems.Count;
end;

function TKsCustomItem.GetItem(Index: Integer): TKsCustomItem;
begin
  if (Index < 0) or (Index >= FItems.Count) then ;
//    raise;
  Result := TKsCustomItem(FItems[Index]);
end;

procedure TKsCustomItem.Add(AItem: TKsCustomItem);
begin
  Insert(Count, AItem);
end;

procedure TKsCustomItem.Insert(NewIndex: Integer; AItem: TKsCustomItem);
begin
  if Assigned(AItem.FParent) then
    raise EKsItemError.Create(SKsItemReinserted);

  FItems.Insert(NewIndex, AItem);
  AItem.FParent := Self;
  Change;
end;

procedure TKsCustomItem.Clear;
var
  i: integer;
begin
  if FItems.Count >= 0 then
    for i := 0 to FItems.Count - 1 do
      Delete(0);
end;

procedure TKsCustomItem.Delete(Index: Integer);
begin
  if (Index >= 0) and (Index < Count) then
    Items[Index].FParent := nil;

  FItems.Delete(Index);
  Change;
end;

function TKsCustomItem.IndexOf(AItem: TComponent): Integer;
begin
  Result := FItems.IndexOf(AItem);
end;

procedure TKsCustomItem.Remove(Item: TKsCustomItem);
var
  I: Integer;
begin
  I := IndexOf(Item);
  if I >= 0 then
    Delete(I);
end;

function TKsCustomItem.ContainsItem(AItem: TKsCustomItem): Boolean;
begin
  while Assigned(AItem) and (AItem <> Self) do
    AItem := AItem.Parent;
  Result := Assigned(AItem);
end;

procedure TKsCustomItem.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
end;

procedure TKsCustomItem.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
end;

{ Popup }

function TKsCustomItem.GetOwnerCustomForm: TComponent;
var
  Form: TWinControl;
  i: integer;
begin
  Result := nil;

  if FOwnerForm = nil then Exit;

  Form := FOwnerForm as TWinControl;
  if Form <> nil then
  begin
    for i := 0 to Form.ComponentCount - 1 do
      if Form.Components[i] is TKsCustomForm then
      begin
        Result := Form.Components[i] as TKsCustomForm;
        Exit;
      end;
  end
  else
    Result := nil;
end;

procedure TKsCustomItem.Popup(View: TKsItemView; X, Y: integer; Rect: TRect);
var
  PopupWindow: TKsPopupWindow;
  ScreenR: TRect;
  OwnerCustomForm: TKsCustomForm;
begin
  PopupWindow := TKsPopupWindowClass(GetPopupWindowClass).CreatePopupWindow(Owner, Self, View);
  try
    { Calc position }
    ScreenR := Classes.Rect(0, 0, Screen.Width, Screen.Height);

    if RectWidth(Rect) > 0 then
    begin
      if View.FIsMenuBar then
      begin
        PopupWindow.SetBounds(Rect.Left, Rect.Bottom, 0, 0);
        PopupWindow.CalcSize;

        if PopupWindow.Left + PopupWindow.Width > ScreenR.Right then
          PopupWindow.Left := Rect.Right - PopupWindow.Width;
        if PopupWindow.Top + PopupWindow.Height > ScreenR.Bottom then
          PopupWindow.Top := Rect.Top - PopupWindow.Height;
      end
      else
      begin
        PopupWindow.SetBounds(Rect.Right, Rect.Top, 0, 0);
        PopupWindow.CalcSize;

        if PopupWindow.Left + PopupWindow.Width > ScreenR.Right then
          PopupWindow.Left := Rect.Left - PopupWindow.Width;
        if PopupWindow.Top + PopupWindow.Height > ScreenR.Bottom then
          PopupWindow.Top := Rect.Bottom - PopupWindow.Height;
      end;

      if PopupWindow.Left + PopupWindow.Width > ScreenR.Right then
        PopupWindow.Left := ScreenR.Right - PopupWindow.Width;
      if PopupWindow.Top + PopupWindow.Height > ScreenR.Bottom then
        PopupWindow.Top := ScreenR.Bottom - PopupWindow.Height;
    end
    else
    begin
      PopupWindow.SetBounds(X, Y, 0, 0);
      PopupWindow.CalcSize;

      if PopupWindow.Left + PopupWindow.Width > ScreenR.Right then
        PopupWindow.Left := X - PopupWindow.Width;
      if PopupWindow.Top + PopupWindow.Height > ScreenR.Bottom then
        PopupWindow.Top := Y - PopupWindow.Height;
    end;

    { Disable Owner Drawing }
    OwnerCustomForm := TKsCustomForm(GetOwnerCustomForm);
    if OwnerCustomForm <> nil then
      OwnerCustomForm.DisableDrawing;
    { Show Popup Window }
    PopupWindow.Visible := true;
    { Set foreground in application not active }
    if FTray then
      SetForegroundWindow(PopupWindow.Handle);
    { Enable Owner Drawing }
    if OwnerCustomForm <> nil then
      OwnerCustomForm.EnableDrawing;
    { Set internal properties }
    if View <> nil then
      View.FChildView := PopupWindow.View;

    PopupWindow.EnterLoop;

    if View <> nil then
      View.FChildView := nil;
  finally
    PopupWindow.Free;
  end;
end;

{ Measure and Drawing }

function TKsCustomItem.UseRightToLeftReading: Boolean;
begin
  Result := SysLocale.MiddleEast and (BiDiMode <> bdLeftToRight);
end;

function TKsCustomItem.UseRightToLeftAlignment: Boolean;
begin
  Result := SysLocale.MiddleEast and (BiDiMode = bdRightToLeft);
end;

function TKsCustomItem.DrawTextBiDiModeFlags(Flags: Longint): Longint;
begin
  Result := Flags;
  { do not change center alignment }
  if UseRightToLeftAlignment then
    if Result and DT_RIGHT = DT_RIGHT then
      Result := Result and not DT_RIGHT { removing DT_RIGHT, makes it DT_LEFT }
    else if not (Result and DT_CENTER = DT_CENTER) then
      Result := Result or DT_RIGHT;
  Result := Result or DrawTextBiDiModeFlagsReadingOnly;
end;

function TKsCustomItem.DrawTextBiDiModeFlagsReadingOnly: Longint;
begin
  if UseRightToLeftReading then
    Result := DT_RTLREADING
  else
    Result := 0;
end;

procedure TKsCustomItem.CalcSize(Buffer: TKsBitmap; View: TKsItemView; var AWidth, AHeight: integer);
  procedure GetMenuSize;
  var
    NonClientMetrics: TNonClientMetrics;
  begin
    NonClientMetrics.cbSize := sizeof(NonClientMetrics);
    if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0) then
    begin
      AWidth := NonClientMetrics.iMenuWidth;
      AHeight := NonClientMetrics.iMenuHeight;
    end;
    if AWidth < 20 then AWidth := 20;
    if AHeight < 20 then AHeight := 20;
  end;

begin
  GetMenuSize;

  if Buffer = nil then Exit;

  Buffer.Font.Assign(MenuFont);

  if View.FIsMenuBar then
  begin
    AWidth := Buffer.TextWidth(FCaption) + 12;

    if FMDIItemKind <> mikNone then
      AWidth := AHeight;
  end
  else
  begin
    AWidth := GlyphWidth + Buffer.TextWidth(FCaption) + ItemStep + SubMenuWidth;
    if FShortCut <> 0 then
      AWidth := AWidth + Buffer.TextWidth(ShortCutToText(FShortCut));
    { Separator }
    if Caption = '-' then
      AHeight := SepHeight;
  end;
end;

procedure TKsCustomItem.DrawItem(Buffer: TKsBitmap; View: TKsItemView; Rect: TRect;
  Active, Hover: boolean);
const
  CheckMarkPoints: array[0..11] of TPoint = (
    { Black }
    (X: -2; Y: -2), (X: 0; Y:  0), (X:  4; Y: -4),
    (X:  4; Y: -3), (X: 0; Y:  1), (X: -2; Y: -1),
    (X: -2; Y: -2),
    { White }
    (X: -3; Y: -2), (X: -3; Y: -1), (X: 0; Y: 2),
    (X:  5; Y: -3), (X:  5; Y: -5));
var
  R: TRect;
  S: string;
  i, j: integer;
  Color: TKColor;
  Pixel: PKColor;
  Canvas: TCanvas;
  Points: array[0..11] of TPoint;
  Flags, X, Y: integer;
  IHandle, IHandle2 : HICON;
  IconX, IconY : integer;
  ID: boolean;
begin
  Buffer.Font.Name := 'Tahome';
  Buffer.Font.Size := 8;

  if View.FIsMenuBar then
  begin
    { MDI Items }
    if FMDIItemKind <> mikNone then
    begin
      Buffer.FillRect(Rect, KColor(clMenu));

      Flags := DFCS_BUTTONPUSH;
      
      if Active then
        if Hover then
          Flags := Flags or DFCS_FLAT
        else
          Flags := Flags or DFCS_PUSHED;

      case FMDIItemKind of
        mikSysMenu: if (FActiveMDIForm <> nil) and (FActiveMDIForm is TKsCustomForm) then
        begin
          ID := false;
          if (FActiveMDIForm as TKsCustomForm).Form.Icon.Handle <> 0 then
            IHandle := (FActiveMDIForm as TKsCustomForm).Form.Icon.Handle
          else
            if Application.Icon.Handle <> 0 then
              IHandle := Application.Icon.Handle
            else
            begin
              IHandle := LoadIcon(0, IDI_APPLICATION);
              ID := true;
            end;
          IconX := GetSystemMetrics(SM_CXSMICON);
          if IconX = 0 then IconX := GetSystemMetrics(SM_CXSIZE);
          IconY := GetSystemMetrics(SM_CYSMICON);
          if IconY = 0 then IconY := GetSystemMetrics(SM_CYSIZE);
          IHandle2 := CopyImage(IHandle, IMAGE_ICON, IconX, IconY, LR_COPYFROMRESOURCE);

          if Active then
            if Hover then
              Buffer.DrawEdge(Rect, KColor(clBtnHighlight), KColor(clBtnShadow))
            else
              Buffer.DrawEdge(Rect, KColor(clBtnShadow), KColor(clBtnHighlight));
              
          InflateRect(Rect, -1, -1);
          DrawIconEx(Buffer.DC, Rect.Left, Rect.Top, IHandle2, 0, 0, 0, 0, DI_NORMAL);

          DestroyIcon(IHandle2);
          if ID then DestroyIcon(IHandle);
        end;
        mikMinimize: begin
          InflateRect(Rect, -2, -2);
          Inc(Rect.Right);
          Dec(Rect.Bottom);

          Flags := Flags or DFCS_CAPTIONMIN;
          DrawFrameControl(Buffer.DC, Rect, DFC_CAPTION, Flags);
        end;
        mikRestore: begin
          InflateRect(Rect, -2, -2);
          Inc(Rect.Right);
          Dec(Rect.Bottom);

          Flags := Flags or DFCS_CAPTIONRESTORE;
          DrawFrameControl(Buffer.DC, Rect, DFC_CAPTION, Flags);
        end;
        mikClose: begin
          InflateRect(Rect, -2, -2);
          Inc(Rect.Right);
          Dec(Rect.Bottom);

          Flags := Flags or DFCS_CAPTIONCLOSE;
          DrawFrameControl(Buffer.DC, Rect, DFC_CAPTION, Flags);
        end;
      end;

      Exit;
    end;
    { Satadard }
    Buffer.FillRect(Rect, KColor(clMenu));
    if Active then
    begin
      if Hover then
        Buffer.DrawEdge(Rect, KColor(clBtnHighlight), KColor(clBtnShadow))
      else
        Buffer.DrawEdge(Rect, KColor(clBtnShadow), KColor(clBtnHighlight));
    end;
    Buffer.Font.Color := clMenuText;
    Buffer.DrawText(Caption, Rect, DrawTextBiDiModeFlags(DT_CENTER or DT_VCENTER or DT_SINGLELINE));
  end
  else
  begin
    { Separator }
    if Caption = '-' then
    begin
      Buffer.FillRect(Rect, KColor(clMenu));
      InflateRect(Rect, -2, -((SepHeight div 2)));
      Rect.Bottom := Rect.Top + 2;
      Buffer.DrawEdge(Rect, KColor(clBtnShadow), KColor(clBtnHighlight));
    end
    else
    begin
      if Active then
      begin
        Color := KColor(clHighlight);
        Buffer.FillRect(Rect, Color);
        Buffer.Font.Color := clHighlightText;
      end
      else
      begin
        Color := KColor(clMenu);
        Buffer.Font.Color := clMenuText;
        Buffer.FillRect(Rect, Color);
      end
    end;

    { Draw Image and Checked }
    if (FChecked) or ((ImgList <> nil) and (FImageIndex >= 0)) then
    begin
      { Draw Edge }
      R := Rect;
      R.Right := R.Left + GlyphWidth;

      Buffer.FillRect(R, KColor(clBtnFace));

      R.Right := R.Left + RectHeight(R);
      if Checked then
        if Active then
        begin
          { Checked and Active }
          Buffer.DrawEdge(R, KColor(clBtnShadow), KColor(clBtnHighlight))
        end
        else
        begin
          { Checked and Inactive }
          Buffer.FillHalftoneRect(R, KColor(clBtnFace), ckWhite);
          Buffer.DrawEdge(R, KColor(clBtnShadow), KColor(clBtnHighlight))
        end
      else
        if Active then
        begin
          { Unchecked and Active }
          Buffer.DrawEdge(R, KColor(clBtnHighlight), KColor(clBtnShadow));
        end;

      { Draw Glyph }
      Canvas := TCanvas.Create;
      try
        Canvas.Handle := Buffer.DC;

        if (ImgList <> nil) and (FImageIndex >= 0) then
        begin
          { Draw Image }
          R := Rect;
          R.Right := R.Left + RectHeight(R);
          ImgList.Draw(Canvas,
            R.Left + ((R.Right - R.Left) - ImgList.Width) div 2,
            R.Top + ((R.Bottom - R.Top) - ImgList.Height) div 2,
            FImageIndex, FEnabled);
        end
        else
        begin
          { Draw CheckBox }
          X := (R.Left + R.Right) div 2 - 2;
          Y := (R.Top + R.Bottom) div 2 + 1;
          System.Move(CheckMarkPoints, Points, 12 * SizeOf(TPoint));
          for i := Low(Points) to High(Points) do
          begin
            Inc (Points[I].X, X);
            Inc (Points[I].Y, Y);
          end;
          Canvas.Pen.Color := clBlack;
          Polyline(Canvas.Handle, Points[0], 7);
          Canvas.Pen.Color := clWhite;
          Polyline(Canvas.Handle, Points[7], 5);
        end;
      finally
        Canvas.Handle := 0;
        Canvas.Free;
      end;
    end;

    { Draw Caption }
    R := Rect;
    Inc(R.Left, GlyphWidth + 4);

    { Check Enabled }
    if not Enabled then
    begin
      if not Active then
      begin
        OffsetRect(R, 1, 1);
        Buffer.Font.Color := clBtnHighlight;
        Buffer.DrawText(FCaption, R, DrawTextBiDiModeFlags(DT_VCENTER or DT_SINGLELINE));
        OffsetRect(R, -1, -1);
      end;
      Buffer.Font.Color := clBtnShadow;
    end;
    { Draw Caption }
    Buffer.DrawText(FCaption, R, DrawTextBiDiModeFlags(DT_VCENTER or DT_SINGLELINE));
    { Draw ShortCut }
    if FShortCut <> 0 then
    begin
      S := ShortCutToText(FShortCut);
      Dec(R.Right, SubMenuWidth);
      { Check Enabled }
      if not Enabled then
      begin
        if not Active then
        begin
          OffsetRect(R, 1, 1);
          Buffer.Font.Color := clBtnHighlight;
          Buffer.DrawText(S, R, DrawTextBiDiModeFlags(DT_RIGHT or DT_VCENTER or DT_SINGLELINE));
          OffsetRect(R, -1, -1);
        end;
        Buffer.Font.Color := clBtnShadow;
      end;
      { Draw Shortcut }
      Buffer.DrawText(S, R, DrawTextBiDiModeFlags(DT_RIGHT or DT_VCENTER or DT_SINGLELINE));
    end;
    { Draw Submenu Arrow }
    if Count > 0 then
    begin
      R := Rect;
      Dec(R.Right, 2);
      R.Left := R.Right - SubMenuWidth;
      DrawFrameControl(Buffer.DC, R, DFC_MENU, DFCS_MENUARROW);
      for i := R.Left to R.Right-1 do
        for j := R.Top to R.Bottom-1 do
        begin
          Pixel := @Buffer.Bits[i + j * Buffer.Width];
          if Pixel^ = KColor(255,255,255,0) then
            Pixel^ := Color
          else
            Pixel^ := KColor(Buffer.Font.Color);
        end;
    end;
  end;
end;

{ Class methods }

class function TKsCustomItem.GetPopupWindowClass: TClass;
begin
  Result := TKsPopupWindow;
end;

{ Properties }

function TKsCustomItem.GetImgList: TCustomImageList;
var
  Item: TKsCustomItem;
begin
  if FImages <> nil then
    Result := FImages
  else
  begin
    { Find root item }
    Item := Self;
    while Item.FParent <> nil do
      Item := Item.FParent;

    Result := Item.Images;
  end;
end;

procedure TKsCustomItem.SetPopupBlend(const Value: boolean);
var
  i: integer;
begin
  FPopupBlend := Value;
  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].PopupBlend := Value;
end;

procedure TKsCustomItem.SetPopupBlendValue(const Value: integer);
var
  i: integer;
begin
  FPopupBlendValue := Value;
  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].PopupBlendValue := Value;
end;

procedure TKsCustomItem.SetIsToolbar(const Value: boolean);
var
  i: integer;
begin
  FIsToolbar := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].FIsToolbar := Value;
end;

procedure TKsCustomItem.SetCaption (Value: String);
begin
  if FCaption <> Value then
  begin
    FCaption := Value;
    Change;
  end;
end;

procedure TKsCustomItem.SetChecked (Value: Boolean);
begin
  if FChecked <> Value then
  begin
    FChecked := Value;
    Change;
  end;
end;

procedure TKsCustomItem.SetEnabled (Value: Boolean);
begin
  if FEnabled <> Value then
  begin
    FEnabled := Value;
    Change;
  end;
end;

procedure TKsCustomItem.SetImageIndex (Value: Integer);
begin
  if FImageIndex <> Value then
  begin
    FImageIndex := Value;
    Change;
  end;
end;

procedure TKsCustomItem.SetImages(Value: TCustomImageList);
begin
  if FImages <> Value then
  begin
    FImages := Value;
    if Assigned(Value) then
      Value.FreeNotification(Self);
    Change;
  end;
end;

procedure TKsCustomItem.SetVisible (Value: Boolean);
begin
  if FVisible <> Value then begin
    FVisible := Value;
    Change;
  end;
end;

procedure TKsCustomItem.SetActiveMDIForm(const Value: TComponent);
var
  i: integer;
begin
  FActiveMDIForm := Value;
  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].ActiveMDIForm := Value;
end;


function TKsCustomItem.FindItemByChar(C: Char): TKsCustomItem;
var
  I: LongInt;
begin
  Result:= nil;
  for I:= 0 to Count - 1 do
    if Pos(AnsiUpperCase('&' + C), AnsiUpperCase(Items[i].Caption)) > 0 then
    begin
      Result:= Items[i];
      Break;
    end;
end;

function TKsCustomItem.FindItem(Value: Integer; Kind: TFindItemKind): TKsCustomItem;
var
  FoundItem: TKsCustomItem;

  function Find(Item: TKsCustomItem): Boolean;
  var
    I: Integer;
  begin
    Result := False;
    if ((Kind = fkShortCut) and (Value = Item.ShortCut)) then
    begin
      FoundItem := Item;
      Result := True;
      Exit;
    end
    else
      if ((Kind = fkHandle) and (Value = Integer(@Item.OnClick))) and (Assigned(Item.OnClick)) then
      begin
        FoundItem := Item;
        Result := True;
        Exit;
      end
      else
        for I := 0 to Item.Count - 1 do
        if Find(Item[I]) then
        begin
          Result := True;
          Exit;
        end;
  end;

begin
  FoundItem := nil;
  Find(Self);
  Result := FoundItem;
end;

function TKsCustomItem.IsShortCut(var Msg: TWMKey): boolean;
type
  TClickResult = (crDisabled, crClicked, crShortCutMoved, crShortCutFreed);
const
  AltMask = $20000000;
var
  ShortCut: TShortCut;
  ClickResult: TClickResult;
  ShortCutItem: TKsCustomItem;

  function NthParentOf(Item: TKsCustomItem; N: Integer): TKsCustomItem;
  begin
    Result := Item;
    while (N > 0) and (Result <> nil) do
    begin
      Result := Result.Parent;
      Dec(N);
    end;
  end;

  function DoClick(var Item: TKsCustomItem; Level: Integer): TClickResult;
  var
    ItemParent: TKsCustomItem;
  begin
    Result := crClicked;
    ItemParent := Item.Parent;
    // Assert(Item = NthParentOf(ShortCutItem, Level));
    if ItemParent <> nil then Result := DoClick(ItemParent, Level + 1);
    if Result in [crDisabled, crShortCutFreed] then Exit;
    if Result = crShortCutMoved then
    begin
      // Shortcut moved, we need to refind the shortcut and restore Item
      // to point to the parent at the right level, if possible
      if (ShortCutItem = nil) or (ShortCutItem.ShortCut <> ShortCut) then
      begin
        ShortCutItem := FindItem(ShortCut, fkShortCut);
        if ShortCutItem = nil then
        begin
          Result := crShortCutFreed;
          Exit; // Shortcut item could not be found
        end;
      end;
      Item := NthParentOf(ShortCutItem, Level);
      if (Item = nil) or (Item.Parent <> ItemParent) then
        Exit; // Shortcut moved in structure, level not correct
      if Level = 0 then Result := crClicked;
    end;
    if Item.Enabled then
      try
        Item.DoClick;
        if (ShortCutItem = nil) or
          ((Item <> ShortCutItem) and (ShortCutItem.ShortCut <> ShortCut)) then
          Result := crShortCutMoved;
      except
        Application.HandleException(Self);
      end
    else Result := crDisabled;
  end;

begin
  ShortCut := Byte(Msg.CharCode);
  if GetKeyState(VK_SHIFT) < 0 then Inc(ShortCut, scShift);
  if GetKeyState(VK_CONTROL) < 0 then Inc(ShortCut, scCtrl);
  if Msg.KeyData and AltMask <> 0 then Inc(ShortCut, scAlt);
  repeat
    ClickResult := crDisabled;
    ShortCutItem := FindItem(ShortCut, fkShortCut);
    if ShortCutItem <> nil then
    begin
      {$IFNDEF KS_COMPILER4}
      ShortCutItems.Push(@ShortCutItem);
      {$ENDIF}
      try
        ClickResult := DoClick(ShortCutItem, 0);
      finally
        {$IFNDEF KS_COMPILER4}
        ShortCutItems.Pop;
        {$ENDIF}
      end;
    end;
  until ClickResult <> crShortCutMoved;
  Result := ShortCutItem <> nil;
end;

function TKsItemView.CheckDropDown: Boolean;
begin
  if (FSelItem <> nil) and (FSelItem.Count > 0) then
    Result := true
  else
    Result := false;
end;

procedure TKsCustomItem.SetFirst(const Value: boolean);
var
  i: integer;
begin
  FFirst := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].First := Value;
end;

procedure TKsCustomItem.SetBiDiMode(const Value: TBiDiMode);
var
  i: integer;
begin
  FBiDiMode := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].FBiDiMode := Value;
end;

procedure TKsCustomItem.SetOnlyFirst(const Value: boolean);
var
  i: integer;
begin
  FOnlyFirst := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].OnlyFirst := Value;
end;

procedure TKsCustomItem.SetDropShadow(const Value: boolean);
var
  i: integer;
begin
  FDropShadow := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].DropShadow := Value;
end;

procedure TKsCustomItem.SetShadowWidth(const Value: integer);
var
  i: integer;
begin
  FShadowWidth := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].ShadowWidth := Value;
end;

procedure TKsCustomItem.SetShowAnimation(const Value: TKsAnimation);
var
  i: integer;
begin
  FShowAnimation := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].ShowAnimation := Value;
end;

procedure TKsCustomItem.SetShowAnimationTime(const Value: integer);
var
  i: integer;
begin
  FShowAnimationTime := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].ShowAnimationTime := Value;
end;

procedure TKsCustomItem.SetOwnerForm(const Value: TWinControl);
var
  i: integer;
begin
  FOwnerForm := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].OwnerForm := Value;
end;

procedure TKsCustomItem.SetTray(const Value: boolean);
var
  i: integer;
begin
  FTray := Value;

  if Count > 0 then
    for i := 0 to Count-1 do
      Items[i].Tray := Value;
end;

{ TKsItemView =================================================================}

constructor TKsItemView.CreateView(AOwner: TComponent; AIsMenuBar: boolean;
  AOrientation: TKsViewOrientation);
begin
  inherited Create(AOwner);
  FIsMenuBar := AIsMenuBar;
  FOrientation := AOrientation;
  FChildView:= nil;
end;

destructor TKsItemView.Destroy;
begin
  inherited;
end;

procedure TKsItemView.CalcSize;
var
  Size: TPoint;
  W, H: integer;
  i: integer;
begin
  if FItems = nil then Exit;

  Size := Point(0, 0);
  W := 0;
  H := 0;

  { Calculation }
  for i := 0 to FItems.Count-1 do
  begin
    if not FItems[i].Visible then Continue;
    FItems[i].CalcSize(Buffer, Self, W, H);

    case FOrientation of
      voVertical: begin
        if W > Size.X then Size.X := W;
        Inc(Size.Y, H);
      end;
      voHorizontal: begin
        if H > Size.Y then Size.Y := H;
        Inc(Size.X, W);
      end;
    end;
  end;

  { set size }
  SetSize(Size);
end;

{ Virtual methids }

procedure TKsItemView.SetPosition(APosition: TPoint);
begin

end;

procedure TKsItemView.SetSize(ASize: TPoint);
begin

end;

{ Drawing }

procedure TKsItemView.Paint(Buffer: TKsBitmap);
var
  i: integer;
  R: TRect;
begin
  if FItems = nil then Exit;

  for i := 0 to FItems.Count-1 do
  begin
    if not FItems[i].Visible then Continue;
    R := GetItemRect(FItems[i]);
    Items[i].DrawItem(Buffer, Self, R, Items[i] = FSelItem, not FLoop);
  end;
end;

procedure TKsItemView.Invalidate;
begin
end;

{ Mouse }

procedure TKsItemView.UpdateHover(P: TPoint);
var
  i: integer;
begin
  if (FWindow <> nil) and (FItems <> nil) then
  begin
    P := FWindow.ScreenToClient(P);

    if not FLoop then
      FSelItem := nil;
    for i := 0 to FItems.Count-1 do
      if FItems[i].Visible and PtInRect(GetItemRect(FItems[i]), P) then
      begin
        FSelItem := FItems[i];
        Break;
      end;
    Invalidate;
  end;
end;

{ Routines }

function TKsItemView.GetItemRect(Item: TKsCustomItem): TRect;
var
  i: integer;
  W, H: integer;
  MaxWidth: integer;
  Size: TPoint;
begin
  Result := Rect(0, 0, 0, 0);
  Size := Point(0, 0);

  if Item = nil then Exit;
  if not Item.Visible then Exit;
   
  MaxWidth := 0;

  if Item.MDIItemKind <> mikNone then
  begin
    Item.CalcSize(FBuffer, Self, W, H);
    case Item.MDIItemKind of
      mikSysMenu: begin
        Result := Rect(0, 0, W,H);
      end;
      mikMinimize: begin
        Result := Rect(TWinControl(FParentComponent).Width - W * 3,
          0,
          TWinControl(FParentComponent).Width - W * 2,
          TWinControl(FParentComponent).Height);
      end;
      mikRestore: begin
        Result := Rect(TWinControl(FParentComponent).Width - W * 2,
          0,
          TWinControl(FParentComponent).Width - W,
          TWinControl(FParentComponent).Height);
      end;
      mikClose: begin
        Result := Rect(TWinControl(FParentComponent).Width - W,
          0,
          TWinControl(FParentComponent).Width,
          TWinControl(FParentComponent).Height);
      end;
    end;
    Exit;
  end;

  for i := 0 to FItems.Count-1 do
  begin
    if not FItems[i].Visible then Continue;

    FItems[i].CalcSize(FBuffer, Self, W, H);
    if W > MaxWidth then MaxWidth := W;

    case FOrientation of
      voVertical: begin
        if W > Size.X then Size.X := W;
        Inc(Size.Y, H);

        if Item = FItems[i] then
        begin
          Result.TopLeft := Point(FLeft, FTop + Size.Y - H);
          Result.BottomRight := Point(FLeft + Size.X, FTop + Size.Y);
        end;
      end;
      voHorizontal: begin
        if H > Size.Y then Size.Y := H;
        Inc(Size.X, W);

        if Item = FItems[i] then
        begin
          Result.TopLeft := Point(FLeft + Size.X - W, FTop);
          Result.BottomRight := Point(FLeft + Size.X, FTop + H);
        end;
      end;
    end;
  end;

  { Set width for popup }
  if FOrientation = voVertical then
    Result.Right := Result.Left + MaxWidth;
end;

function TKsItemView.Popup: Boolean;

  function GetPopupPos: TPoint;
  begin
    with GetItemRect(FSelItem) do
      if FIsMenuBar then
        Result := Point(Left, Bottom)
      else
        Result := Point(Right, Top);

    if FWindow <> nil then
      Result := FWindow.ClientToScreen(Result);
  end;

var
  R: TRect;
begin
  if not FSelItem.Enabled then Exit;

  Result:= CheckDropDown;
  if Result then
    with GetPopupPos do
    begin
      R := GetItemRect(FSelItem);
      R.TopLeft := Window.ClientToScreen(R.TopLeft);
      R.BottomRight := Window.ClientToScreen(R.BottomRight);
      FDropDown:= true;
      FSelItem.Popup(Self, X, Y, R);
    end;
end;

procedure TKsItemView.SelectNext;
var
  i: integer;
begin
  i := FItems.IndexOf(FSelItem);
  if i < FItems.Count - 1 then FSelItem := FItems[i + 1]
  else FSelItem := FItems[0];

  if not FSelItem.Visible or (FSelItem.Caption = '-') then
  begin
    SelectNext;
    Exit;
  end;

  Invalidate;
  if FDropDown then Popup;
end;

procedure TKsItemView.SelectPrev;
var
  i: integer;
begin
  i := FItems.IndexOf(FSelItem);
  if i > 0 then FSelItem := FItems[i - 1]
  else FSelItem := FItems[FItems.Count-1];

  if not FSelItem.Visible or (FSelItem.Caption = '-') then
  begin
    SelectPrev;
    Exit;
  end;

  Invalidate;
  if FDropDown then Popup;
end;

{ Messages Loop }

const
  CM_POPUP = WM_USER + 111;

function TKsItemView.MessageLoop: Boolean;

 procedure EndLoop;
 var
   View: TKsItemView;
 begin
   View := Self;
   while View <> nil do
   begin
     View.FLoop := false;
     View.FSelItem := nil;
     View.FDropDown:= false;
     View.Invalidate;
     View := View.FParentView;
   end;
 end;

 function ContinueLoop: boolean;
 begin
   Result := FLoop and (GetCapture = CaptureHandle);
 end;

var
  FirstLoop: Boolean;
  Msg: TMsg;
  P, P2: TPoint;
  Item: TKsCustomItem;
  View: TKsItemView;
  View1: TKsItemView;
  F: Boolean;
begin
  Result := True;
  try
    FLoop := true;

    if {FIsMenuBar and (}FItems.Count > 0{)} then
    begin
      FSelItem := FItems[FItems.Count - 1];
      SelectNext;
      GetCursorPos(P);
      UpdateHover(P);
      Invalidate;
    end;

    if Window <> nil then
      CaptureMouse(Window.Handle);
      
    FirstLoop := True;
    while ContinueLoop do
    begin
      if not FirstLoop then
        WaitMessage;
      FirstLoop := False;
      while ContinueLoop and PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) do
      begin
        case Msg.message of
          WM_WINDOWPOSCHANGING: begin
            EndLoop;
            Exit;
          end;
          WM_LBUTTONDOWN: begin
            P := SmallPointToPoint(TSmallPoint(Msg.lParam));
            ClientToScreen(Msg.hwnd, P);
            P2 := P;
            P:= FWindow.ScreenToClient(P);

            if (FSelItem <> nil) and PtInRect(GetItemRect(FSelItem), P) then
              PostMessage(Window.Handle, WM_KEYDOWN, VK_RETURN, 0)
            else
            begin
              View := Self;
              F := false;
              while (View <> nil) and not f do
              begin
                P := View.Window.ScreenToClient(P2);
                if PtInRect(View.GetItemRect(View.FSelItem), P) then
                  f := true;
                View := View.FParentView;
              end;
              if not f then
              begin
                EndLoop;
                Exit;
              end;
            end;
          end;
          WM_QUIT, WM_NCLBUTTONDOWN..WM_NCMBUTTONDBLCLK:
            begin
              EndLoop;
              Continue;
            end;
        end;
        if PeekMessage(Msg, 0, 0, 0, PM_REMOVE or PM_NOYIELD) then
        begin
          case Msg.message of
            CM_POPUP: Popup;
            WM_CONTEXTMENU: ;
            WM_KEYFIRST..WM_KEYLAST:
              if (GetKeyState(VK_LBUTTON) >= 0) then
                case Msg.message of
                  WM_KEYDOWN, WM_SYSKEYDOWN:
                    case Msg.wParam of
                      VK_TAB: begin
                      end;
                      VK_RETURN: begin
                        if (FSelItem <> nil) then
                          FSelItem.DoClick;
                        if not Popup and (FSelItem <> nil) and
                          (FSelItem.Caption <> '-') and (FSelItem.Enabled) then
                            EndLoop;
                      end;
                      VK_SPACE: begin
                      end;
                      VK_ESCAPE: begin
                        if FParentView <> nil then
                          FParentView.FDropDown := false;
                        FSelItem := nil;
                        Invalidate;
                        Exit;
                      end;
                      VK_LEFT: begin
                        if FIsMenuBar then
                        begin
                          SelectPrev;
                        end
                        else
                          if (FParentView <> nil) then
                            if (FParentView.FIsMenuBar) then
                            begin
                              PostMessage(FParentView.Window.Handle, WM_KEYDOWN, VK_LEFT, 0);
                              Exit;
                            end
                            else
                            begin
                              PostMessage(Window.Handle, WM_KEYDOWN, VK_ESCAPE, 0);
                            end;
                      end;
                      VK_RIGHT: begin
                        if FIsMenuBar then
                        begin
                          SelectNext;
                        end
                        else
                          if (FParentView <> nil) then
                            if (FParentView.FIsMenuBar) then
                            begin
                              PostMessage(FParentView.Window.Handle, WM_KEYDOWN, VK_RIGHT, 0);
                              Exit;
                            end
                            else
                            begin
                              PostMessage(Window.Handle, WM_KEYDOWN, VK_ESCAPE, 0);
                            end;
                      end;
                      VK_UP:
                        if not FIsMenuBar then
                          SelectPrev;
                      VK_DOWN:
                        if not FIsMenuBar then
                          SelectNext
                        else
                          Popup;
                      VK_MENU, VK_F10:
                        EndLoop;
                    else
                      TranslateMessage(Msg);
                    end;
                  WM_CHAR, WM_SYSCHAR: begin
                    if FItems.FindItemByChar(Char(Msg.wParam)) <> nil then
                    begin
                      FSelItem:= FItems.FindItemByChar(Char(Msg.wParam));
                      Invalidate;
                      PostMessage(Window.Handle, WM_KEYDOWN, VK_RETURN, 0)
                    end;
                  end;
                end;
            WM_MOUSEFIRST..WM_MOUSELAST:
              case Msg.message of
                WM_MOUSEMOVE: begin
                  P := SmallPointToPoint(TSmallPoint(Msg.lParam));
                  ClientToScreen(Msg.hwnd, P);
                  if (P.X <> LastPos.X) or (P.Y <> LastPos.Y) then
                  begin
                    // Find top level menu
                    View := Self;
                    while View.FParentView <> nil do
                      View := View.FParentView;
                    // check all items
                    while View <> nil do
                    begin
                      Item := View.FSelItem;
                      View.UpdateHover(P);
                      if (Item <> View.FSelItem) and
                         (View <> Self) then
                        begin
                          View1:= Self;
                          while View1 <> View do
                          begin
                            PostMessage(View1.Window.Handle, WM_KEYDOWN, VK_ESCAPE, 0);
                            View1 := View1.FParentView;
                          end;
                          PostMessage(View.Window.Handle, CM_POPUP, 0, 0);
                          Exit;
                        end;
                      View:= View.FChildView;
                    end;
                    if not FIsMenuBar then Popup;
                    LastPos := P;
                  end;
                end;
                WM_LBUTTONDOWN: begin
                end;
                WM_LBUTTONUP: begin
                  P := SmallPointToPoint(TSmallPoint(Msg.lParam));
                  ClientToScreen(Msg.hwnd, P);
                  P2:= P;
                  P := FWindow.ScreenToClient(P);

                  if (FSelItem <> nil) and PtInRect(GetItemRect(FSelItem), P) then
                    PostMessage(Window.Handle, WM_KEYDOWN, VK_RETURN, 0)
                  else
                  begin
                    View:= Self;
                    F:= false;
                    while (View <> nil) and not f do
                    begin
                      P:= View.Window.ScreenToClient(P2);
                      if PtInRect(View.GetItemRect(View.FSelItem), P) then
                        f:= true;
                      View:= View.FParentView;
                    end;
                    if not f then
                      EndLoop;
                  end;
//                      TranslateMessage(Msg);
                end;
              end;
          else
            DispatchMessage(Msg);
          end;
        end;
      end;
    end;
  finally
    FLoop := false;
    EndCapture;
  end;
end;

{ Global routines }

procedure FreeContainerClassList;
var
  i: Integer;
begin
  if Assigned(ContainerClassList) then
  begin
    for i := ContainerClassList.Count-1 downto 0 do
      Dispose(PContainerClassData(ContainerClassList[I]));
    ContainerClassList.Free;
    ContainerClassList := nil;
  end;
end;

initialization
  InitMenuFont;
  RegisterClasses([TKsCustomItem]);

  ClickHandler := TKsClickHandler.CreateHandler;
finalization
  ClickHandler.Free;
  ClickHandler := nil;

  FreeContainerClassList;
  MenuFont.Free;
  MenuFont := nil;
end.
