unit KsMenuDesigner;

interface

{$I KsCommonVer.inc}

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, KsItems, KsMenus, ExtCtrls, ComCtrls, Buttons, Menus,
  {$IFDEF KS_DELPHI6}
  DesignIntf, DesignWindows, DesignEditors
  {$ELSE}
  DsgnIntf, DsgnWnds, LibIntf
  {$ENDIF};

type

  TKsItemEditForm = class(TDesignWindow)
    Timer1: TTimer;
    Pages: TPageControl;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    Panel2: TPanel;
    TreeItems: TTreeView;
    ItemsName: TPanel;
    Panel4: TPanel;
    Panel1: TPanel;
    Panel5: TPanel;
    Bevel1: TBevel;
    btnInsert: TButton;
    btnSubMenu: TButton;
    btnDelete: TButton;
    btnUp: TBitBtn;
    btnDown: TBitBtn;
    Label1: TLabel;
    cbSource: TComboBox;
    Button1: TButton;
    BtnConvert: TButton;
    Label2: TLabel;
    Memo1: TMemo;
    procedure btnInsertClick(Sender: TObject);
    procedure TreeItemsClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure btnSubMenuClick(Sender: TObject);
    procedure TreeItemsEdited(Sender: TObject; Node: TTreeNode;
      var S: String);
    procedure btnDeleteClick(Sender: TObject);
    procedure btnUpClick(Sender: TObject);
    procedure btnDownClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure TreeItemsDblClick(Sender: TObject);
    procedure BtnConvertClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure PagesChange(Sender: TObject);
  private
    { Private declarations }
    FParentComponent: TComponent;
    FRootItem: TKsCustomItem;
    FSelRootItem: TKsCustomItem;
    FSelItem: TKsCustomItem;
    procedure RebuildTreeItem;
    procedure InsertNewItem;
    procedure DoItemsChange(Sender: TObject);
  protected
    procedure Notification (AComponent: TComponent; Operation: TOperation); override;
    function UniqueName (Component: TComponent): String; override;
  public
    { Public declarations }
  end;

{ IDE Editors }

  TKsMenuBarEditor = class(TDefaultEditor)
  public
    procedure Edit; override;
    procedure ExecuteVerb (Index: Integer); override;
    function GetVerb (Index: Integer): String; override;
    function GetVerbCount: Integer; override;
  end;

  TKsPopupMenuEditor = class(TDefaultEditor)
  public
    procedure Edit; override;
    procedure ExecuteVerb (Index: Integer); override;
    function GetVerb (Index: Integer): String; override;
    function GetVerbCount: Integer; override;
  end;

  TKsItemsPropertyEditor = class(TStringProperty)
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
    function GetValue: String; override;
  end;

var
  KsItemEditForm: TKsItemEditForm;

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

uses TypInfo, KsMenuConvert;

{$R *.DFM}

const
  SDefaultMenuItemName = 'CustomItem';

procedure ShowEditForm(const AParentComponent: TComponent; const ARootItem: TKsCustomItem;
    const ADesigner: {$IFDEF KS_DELPHI6} IDesigner); {$ELSE} IFormDesigner); {$ENDIF}
var
  i: integer;
  Form: TCustomForm;
  EditForm: TKsItemEditForm;
begin
  for i := 0 to Screen.FormCount-1 do
  begin
    Form := Screen.Forms[I];
    if Form is TKsItemEditForm then
      if TKsItemEditForm(Form).FRootItem = ARootItem then
      begin
        Form.Show;
        if Form.WindowState = wsMinimized then
          Form.WindowState := wsNormal;
        Exit;
      end;
  end;

  EditForm := TKsItemEditForm.Create(Application);
  try
    EditForm.Designer := ADesigner;
    EditForm.FParentComponent := AParentComponent;
    AParentComponent.FreeNotification(EditForm);
    EditForm.FRootItem := ARootItem;
    EditForm.FRootItem.OnInternalChange := EditForm.DoItemsChange;
    EditForm.FSelRootItem := ARootItem;
    EditForm.FSelItem := ARootItem;

    EditForm.Caption := 'Editing ' + AParentComponent.Name;
    EditForm.ItemsName.Caption := ' ' + AParentComponent.Name + '.Items';

    EditForm.RebuildTreeItem;
    EditForm.ActiveControl := EditForm.TreeItems;

    EditForm.Show;
  except
    EditForm.Free;
    raise;
  end;
end;

{ TfrmMenuDesigner }

procedure TKsItemEditForm.FormCreate(Sender: TObject);
begin
  { Create }
  Button1Click(Self);
end;

procedure TKsItemEditForm.PagesChange(Sender: TObject);
begin
  if Pages.ActivePage.TabIndex = 1 then
    Button1Click(Self);
end;

procedure TKsItemEditForm.Button1Click(Sender: TObject);
var
  Form: TCustomForm;
  i: integer;
begin
  if FParentComponent = nil then Exit;
  if not (FParentComponent.Owner is TCustomForm) then Exit;

  { Rebuild Source List }

  cbSource.Items.Clear;
  Form := TCustomForm(FParentComponent.Owner);

  for i := 0 to Form.ComponentCount - 1 do
  begin
    if Form.Components[i] is TKsCustomPopupMenu then Continue;

    if Form.Components[i] is TMainMenu then
      cbSource.Items.Add(Form.Components[i].Name);

    if Form.Components[i] is TPopupMenu then
      cbSource.Items.Add(Form.Components[i].Name);
  end;
end;

procedure TKsItemEditForm.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  if FParentComponent <> nil then
    if FParentComponent is TWinControl then
      (FParentComponent as TWinControl).Invalidate;
  { Destroy }
  Action := caFree;
end;

procedure TKsItemEditForm.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and
     ((AComponent = FParentComponent) or (AComponent = FRootItem)) then
    Free;
end;

function TKsItemEditForm.UniqueName(Component: TComponent): String;
var
  S: String;
begin
  S := Component.ClassName;
  if (Length(S) > 1) and (UpCase(S[1]) = 'T') then
    System.Delete (S, 1, 1);
  Result := Designer.UniqueName(S);
end;

procedure TKsItemEditForm.DoItemsChange(Sender: TObject);
begin
  { Update if items Change }
  RebuildTreeItem;
end;

procedure TKsItemEditForm.RebuildTreeItem;
  procedure AddItems(Parent: TKsCustomItem; TreeNode: TTreeNode);
  var
    i: integer;
    Node: TTreeNode;
  begin
    for i := 0 to Parent.Count-1 do
    begin
      Node := TreeItems.Items.AddChild(TreeNode, Parent[i].Caption);
      Node.Data := Parent[i];
      if FSelItem = Parent[i] then
        Node.Selected := true;
      if Parent[i].Count > 0 then
      begin
        AddItems(Parent[i], Node);
      end;
    end;
  end;

begin
  if FRootItem = nil then Exit;

  TreeItems.Items.BeginUpdate;
  try
    TreeItems.Items.Clear;

    AddItems(FRootItem, nil);
  finally
    TreeItems.Items.EndUpdate;
  end;
end;

procedure TKsItemEditForm.InsertNewItem;
var
  ItemClass: TKsCustomItemClass;
  Item: TKsCustomItem;
begin
  { Get Item Class }
  ItemClass := GetItemClass(FParentComponent.ClassType);
  if ItemClass <> nil then
  begin
    { Add Item }
    Item := ItemClass.Create(FParentComponent.Owner);
    try
      if Designer <> nil then
        Item.Name := Designer.UniqueName(SDefaultMenuItemName);

      Item.Caption := Item.Name;

      if FSelRootItem <> nil then
      begin
        FSelRootItem.Add(Item);
      end;

      FSelItem := Item;
    except
      raise;
    end;
    Designer.Modified;

    RebuildTreeItem;

    ActiveControl := TreeItems;
  end;
end;

procedure TKsItemEditForm.TreeItemsClick(Sender: TObject);
begin
  { Select Item }
  if TreeItems.Selected = nil then Exit;
  if TreeItems.Selected.Data = nil then Exit;

  FSelItem := TKsCustomItem(TreeItems.Selected.Data);

  if (Designer <> nil) and (FSelItem <> nil) and not(csDestroying in FSelItem.ComponentState) then
    Designer.SelectComponent(FSelItem);
end;

procedure TKsItemEditForm.Timer1Timer(Sender: TObject);
begin
  Exit;
  { Check Enabled State }
  btnDelete.Enabled := not (FSelItem = nil);
  btnInsert.Enabled := not (FSelRootItem = nil);
  btnSubMenu.Enabled := not (FSelRootItem = nil);

  btnUp.Enabled := not ((FSelItem = nil) and (FSelItem.Parent = nil) and
    (FSelItem.Parent.IndexOf(FSelItem) = 0));
  btnDown.Enabled := not ((FSelItem = nil) and (FSelItem.Parent = nil) and
    (FSelItem.Parent.IndexOf(FSelItem) = FSelItem.Parent.Count-1));
end;

procedure TKsItemEditForm.btnInsertClick(Sender: TObject);
begin
  { Insert Item }
  if FSelItem.Parent <> nil then
    FSelRootItem := FSelItem.Parent;
  InsertNewItem;
end;

procedure TKsItemEditForm.btnSubMenuClick(Sender: TObject);
begin
  { Insert Child }
  if TreeItems.Selected = nil then Exit;
  if TreeItems.Selected.Data = nil then Exit;

  FSelRootItem := TKsCustomItem(TreeItems.Selected.Data);
  InsertNewItem;
end;

procedure TKsItemEditForm.btnDeleteClick(Sender: TObject);
var
  Index: integer;
  ParentItem: TKsCustomItem;
begin
  { Delete Item }
  if FSelItem = nil then Exit;
  if FSelItem.Parent = nil then Exit;

  ParentItem := FSelItem.Parent;

  Index := ParentItem.IndexOf(FSelItem); 
  ParentItem.Remove(FSelItem);
  FSelItem.Free;

  if (Index > 0) and (ParentItem.Count > 0) then
    FSelItem := ParentItem[Index-1]
  else
    if (ParentItem.Count > 0) then
      FSelItem := ParentItem[0];

  RebuildTreeItem;

  ActiveControl := TreeItems;
end;

procedure TKsItemEditForm.btnUpClick(Sender: TObject);
var
  Index: integer;
  ParentItem: TKsCustomItem;
begin
  { Up }
  if FSelItem = nil then Exit;
  if FSelItem.Parent = nil then Exit;
  if FSelItem.Parent.IndexOf(FSelItem) = 0 then Exit;

  ParentItem := FSelItem.Parent;

  Index := ParentItem.IndexOf(FSelItem);
  Dec(Index);
  ParentItem.Remove(FSelItem);
  ParentItem.Insert(Index, FSelItem);

  FSelItem := FSelItem;

  RebuildTreeItem;

  ActiveControl := TreeItems;
end;

procedure TKsItemEditForm.btnDownClick(Sender: TObject);
var
  Index: integer;
  ParentItem: TKsCustomItem;
begin
  { Down }
  if FSelItem = nil then Exit;
  if FSelItem.Parent = nil then Exit;
  if FSelItem.Parent.IndexOf(FSelItem) = FSelItem.Parent.Count-1 then Exit;

  ParentItem := FSelItem.Parent;

  Index := ParentItem.IndexOf(FSelItem);
  Inc(Index);
  ParentItem.Remove(FSelItem);
  ParentItem.Insert(Index, FSelItem);

  FSelItem := FSelItem;

  RebuildTreeItem;

  ActiveControl := TreeItems;
end;

procedure TKsItemEditForm.TreeItemsEdited(Sender: TObject; Node: TTreeNode;
  var S: String);
var
  Item: TKsCustomItem;
begin
  { Edit caption }
  if Node = nil then Exit;
  if Node.Data = nil then Exit;

  Item := TKsCustomItem(Node.Data);
  Item.Caption := S;

  Designer.Modified;
end;

procedure TKsItemEditForm.TreeItemsDblClick(Sender: TObject);
var
  SelItem: TKsCustomItem;
  PropCount, I: Integer;
  Props: PPropList;
  PropInfo: PPropInfo;
  MethodName: String;
  Method: TMethod;
begin
  { Add method }
  SelItem := nil;
  if Assigned(TreeItems.Selected) then
    SelItem := TreeItems.Selected.Data;

  if SelItem = nil then Exit;

  PropCount := GetPropList(SelItem.ClassInfo, [tkMethod], nil);
  GetMem(Props, PropCount * SizeOf(PPropInfo));
  try
    GetPropList(FSelItem.ClassInfo, [tkMethod], Props);
    for I := PropCount-1 downto 0 do begin
      PropInfo := Props[I];
      if CompareText(PropInfo.Name, 'OnClick') = 0 then begin
        Method := GetMethodProp(SelItem, PropInfo);
        MethodName := Designer.GetMethodName(Method);
        if MethodName = '' then
        begin
          MethodName := SelItem.Name + 'Click';
          Method := Designer.CreateMethod(MethodName, GetTypeData(PropInfo.PropType^));
          SetMethodProp (SelItem, {$IFDEF KS_D5} PropInfo.Name {$ELSE} PropInfo {$ENDIF},
            Method);
          Designer.Modified;
        end;
        if Designer.MethodExists(MethodName) then
          Designer.ShowMethod (MethodName);
        Break;
      end;
    end;
  finally
    FreeMem (Props);
  end;
end;

procedure TKsItemEditForm.BtnConvertClick(Sender: TObject);
var
  Form: TCustomForm;
  SourceItems: TMenuItem;
  Source: TComponent;
begin
  if FRootItem = nil then Exit;
  if cbSource.ItemIndex < 0 then Exit;
  if cbSource.Items.Count = 0 then Exit;
  if FParentComponent = nil then Exit;
  if not (FParentComponent.Owner is TCustomForm) then
    raise Exception.Create('The converter can only be used on a form');

  Form := TCustomForm(FParentComponent.Owner);

  Source := Form.FindComponent(cbSource.Items[cbSource.ItemIndex]);

  if Source <> nil then
  begin
    if MessageDlg('Convert items from '+(Source as TMenu).Name+'?', mtConfirmation, [mbYes, mbNo, mbCancel], 0) <> mrYes then
      Exit;

    if Source is TMenu then
    begin
      DoConvert(FRootItem, Source as TMenu, FParentComponent.Owner,
        GetItemClass(FParentComponent.ClassType), Memo1.Lines);
    end;

    ShowMessage('Convertion complete!');

    RebuildTreeItem;
  end;
end;



{ IDE Editors =================================================================}

{ TKsMenuBarEditor ============================================================}

procedure TKsMenuBarEditor.Edit;
begin
  if Assigned(Component) then
    ShowEditForm(Component, TKsCustomMenuBar(Component).Items, Designer);
end;

procedure TKsMenuBarEditor.ExecuteVerb (Index: Integer);
begin
  if Index = 0 then
    Edit;
end;

function TKsMenuBarEditor.GetVerbCount: Integer;
begin
  Result := 1;
end;

function TKsMenuBarEditor.GetVerb(Index: Integer): String;
begin
  if Index = 0 then
    Result := 'Edit...'
  else
    Result := '';
end;

{ TKsPopupMenuEditor }

procedure TKsPopupMenuEditor.Edit;
begin
  if Assigned(Component) then
    ShowEditForm(Component, TKsCustomPopupMenu(Component).Items, Designer);
end;

procedure TKsPopupMenuEditor.ExecuteVerb (Index: Integer);
begin
  if Index = 0 then
    Edit;
end;

function TKsPopupMenuEditor.GetVerbCount: Integer;
begin
  Result := 1;
end;

function TKsPopupMenuEditor.GetVerb(Index: Integer): String;
begin
  if Index = 0 then
    Result := 'Edit...'
  else
    Result := '';
end;

{ TKsItemsPropertyEditor }

procedure TKsItemsPropertyEditor.Edit;
var
  Editor: {$IFDEF KS_DELPHI6} IComponentEditor; {$ELSE} TComponentEditor; {$ENDIF} 
begin
  if PropCount <> 1 then Exit;
  Editor := GetComponentEditor(GetComponent(0) as TComponent, Designer);
  try
    Editor.Edit;
  finally
    {$IFNDEF KS_DELPHI6}
    Editor.Free;
    {$ENDIF}
  end;
end;

function TKsItemsPropertyEditor.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paDialog, paReadOnly];
end;

function TKsItemsPropertyEditor.GetValue: String;
begin
  Result := '(Items)';
end;

end.


