{
 *****************************************************************************
  This file is part of the EducationLaz package

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

  Author: Mattias Gaertner

  Abstract:
    Frame to setup the IDE menus.
}
unit EduMenu;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, AVL_Tree,
  // LCL
  LCLProc, LResources, Forms, StdCtrls, ComCtrls, Controls, ExtCtrls,
  // LazUtils
  AvgLvlTree, LazConfigStorage,
  // IdeIntf
  IDEOptionsIntf, IDEOptEditorIntf, MenuIntf, IDEImagesIntf,
  // Education
  EduOptions;

type

  { TEduMenuOptions }

  TEduMenuOptions = class(TEduOptionsNode)
  private
    fHidden: TStringToStringTree;
    function GetMenuHidden(MenuPath: string): boolean;
    procedure SetMenuHidden(MenuPath: string; const AValue: boolean);
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure ClearHMenuidden;
    function Load(Config: TConfigStorage): TModalResult; override;
    function Save(Config: TConfigStorage): TModalResult; override;
    function MenuItemToPath(Item: TIDEMenuItem): string;
    function FindItemWithPath(Path: string): TIDEMenuItem;
    function KeepItemVisible(Item: TIDEMenuItem): boolean;
    procedure Apply(Enable: boolean); override;
    property MenuHidden[MenuPath: string]: boolean read GetMenuHidden write SetMenuHidden;
  end;

  { TEduMenuFrame }

  TEduMenuFrame = class(TAbstractIDEOptionsEditor)
    EduMenuBtnPanel: TPanel;
    MenusGroupBox: TGroupBox;
    MenusTreeView: TTreeView;
    ShowAllButton: TButton;
    ShowAllChildsButton: TButton;
    procedure FrameClick(Sender: TObject);
    procedure MenusTreeViewMouseDown(Sender: TObject; Button: TMouseButton;
      {%H-}Shift: TShiftState; X, Y: Integer);
    procedure ShowAllButtonClick(Sender: TObject);
    procedure ShowAllChildsButtonClick(Sender: TObject);
  private
    ShowImgID: LongInt;
    HalfHideImgID: integer;
    HideImgID: LongInt;
    procedure FillMenuTree;
    procedure SaveMenuTree;
    function TVNodeToIDEMenuPath(TVNode: TTreeNode): string;
    procedure UpdateTVNodeImage(TVNode: TTreeNode);
    function GetCleanCaption(Item: TIDEMenuItem): string;
    function TVNodeToName(TVNode: TTreeNode): string;
  public
    function GetTitle: String; override;
    procedure ReadSettings(AOptions: TAbstractIDEOptions); override;
    procedure Setup({%H-}ADialog: TAbstractOptionsEditorDialog); override;
    class function SupportedOptionsClass: TAbstractIDEOptionsClass; override;
    procedure WriteSettings(AOptions: TAbstractIDEOptions); override;
  end;

var
  EduMenuOptions: TEduMenuOptions = nil;

procedure Register;

implementation

procedure Register;
begin
  EduMenuOptions:=TEduMenuOptions.Create;
  EducationOptions.Root.Add(EduMenuOptions);
  EduOptionMenuID:=RegisterIDEOptionsEditor(EduOptionID,TEduMenuFrame,
                                            EduOptionMenuID)^.Index;
end;

{ TEduMenuFrame }

procedure TEduMenuFrame.FrameClick(Sender: TObject);
begin

end;

procedure TEduMenuFrame.MenusTreeViewMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Node: TTreeNode;
  Hit: THitTests;
begin
  if Button<>mbLeft then exit;
  Node:=MenusTreeView.GetNodeAt(X,Y);
  if (Node=nil) then exit;
  Hit:=MenusTreeView.GetHitTestInfoAt(X,Y);
  if [htOnIcon,htOnStateIcon]*Hit<>[] then begin
    if Node.StateIndex=HideImgID then
      Node.StateIndex:=ShowImgID
    else
      Node.StateIndex:=HideImgID;
    UpdateTVNodeImage(Node);
  end;
end;

procedure TEduMenuFrame.ShowAllButtonClick(Sender: TObject);
var
  TVNode: TTreeNode;
begin
  MenusTreeView.BeginUpdate;
  TVNode:=MenusTreeView.Items.GetFirstNode;
  while TVNode<>nil do begin
    TVNode.StateIndex:=ShowImgID;
    TVNode:=TVNode.GetNext;
  end;
  MenusTreeView.EndUpdate;
end;

procedure TEduMenuFrame.ShowAllChildsButtonClick(Sender: TObject);
var
  TVNode: TTreeNode;
  SelNode: TTreeNode;
begin
  MenusTreeView.BeginUpdate;
  SelNode:=MenusTreeView.Selected;
  if SelNode<>nil then begin
    TVNode:=SelNode.GetFirstChild;
    while (TVNode<>nil) and (TVNode.HasAsParent(SelNode)) do begin
      TVNode.StateIndex:=ShowImgID;
      TVNode:=TVNode.GetNext;
    end;
    UpdateTVNodeImage(SelNode);
  end;
  MenusTreeView.EndUpdate;
end;

procedure TEduMenuFrame.FillMenuTree;

  procedure Add(ParentTVNode: TTreeNode; Item: TIDEMenuItem; var ContainsHidden: boolean);
  var
    TVNode: TTreeNode;
    Section: TIDEMenuSection;
    i: Integer;
    HasHiddenChilds: Boolean;
  begin
    if Item.Name='' then exit;
    TVNode:=MenusTreeView.Items.AddChild(ParentTVNode,Item.Name+': "'+GetCleanCaption(Item)+'"');
    HasHiddenChilds:=false;
    if Item is TIDEMenuSection then begin
      Section:=TIDEMenuSection(Item);
      for i:=0 to Section.Count-1 do
        Add(TVNode,Section[i],HasHiddenChilds);
    end;
    if EduMenuOptions.MenuHidden[EduMenuOptions.MenuItemToPath(Item)] then
      TVNode.StateIndex:=HideImgID
    else if HasHiddenChilds then
      TVNode.StateIndex:=HalfHideImgID
    else
      TVNode.StateIndex:=ShowImgID;
    if TVNode.StateIndex<>ShowImgID then
      ContainsHidden:=true;
  end;

var
  i: Integer;
  Hidden: boolean;
begin
  MenusTreeView.BeginUpdate;
  MenusTreeView.Items.Clear;
  if MenusTreeView.StateImages=nil then
    MenusTreeView.StateImages:=IDEImages.Images_16;
  ShowImgID:=IDEImages.LoadImage('menu_run');
  HalfHideImgID:=IDEImages.LoadImage('menu_run_file');
  HideImgID:=IDEImages.LoadImage('menu_stop');
  Hidden:=false;
  for i:=0 to IDEMenuRoots.Count-1 do
    Add(nil,IDEMenuRoots[i],Hidden);
  MenusTreeView.EndUpdate;
end;

procedure TEduMenuFrame.SaveMenuTree;
var
  TVNode: TTreeNode;
  NewHide: Boolean;
  OldHide: boolean;
  Item: TIDEMenuItem;
  Path: String;
  OldHidden: TStringToStringTree;
begin
  OldHidden:=TStringToStringTree.Create(false);
  try
    OldHidden.Assign(EduMenuOptions.fHidden);
    EduMenuOptions.ClearHMenuidden;
    TVNode:=MenusTreeView.Items.GetFirstNode;
    while TVNode<>nil do begin
      NewHide:=TVNode.StateIndex=HideImgID;
      Path:=TVNodeToIDEMenuPath(TVNode);
      OldHide:=OldHidden.Contains(Path);
      EduMenuOptions.MenuHidden[Path]:=NewHide;
      if NewHide<>OldHide then begin
        Item:=EduMenuOptions.FindItemWithPath(Path);
        if (Item<>nil) and (not EduMenuOptions.KeepItemVisible(Item)) then begin
          Item.Visible:=not NewHide;
          //debugln(['TEduMenuFrame.SaveMenuTree changed visibility: ',Item.GetPath,' visible=',Item.Visible,' Path=',Path,' OldHide=',OldHide,' NewHide=',NewHide]);
        end;
      end;
      TVNode:=TVNode.GetNext;
    end;
  finally
    OldHidden.Free;
  end;
end;

function TEduMenuFrame.TVNodeToIDEMenuPath(TVNode: TTreeNode): string;
begin
  Result:='';
  while TVNode<>nil do begin
    if Result<>'' then
      Result:='/'+Result;
    Result:=TVNodeToName(TVNode)+Result;
    TVNode:=TVNode.Parent;
  end;
end;

procedure TEduMenuFrame.UpdateTVNodeImage(TVNode: TTreeNode);

  function ContainsHiddenNode(Node: TTreeNode): boolean;
  begin
    if (Node.StateIndex=HideImgID) and (Node<>TVNode) then
      exit(true);
    Node:=Node.GetFirstChild;
    while Node<>nil do begin
      if ContainsHiddenNode(Node) then exit(true);
      Node:=Node.GetNextSibling;
    end;
    Result:=false;
  end;

begin
  if TVNode=nil then exit;
  if TVNode.StateIndex=HideImgID then
    TVNode.StateIndex:=HideImgID
  else if ContainsHiddenNode(TVNode) then
    TVNode.StateIndex:=HalfHideImgID
  else
    TVNode.StateIndex:=ShowImgID;
  UpdateTVNodeImage(TVNode.Parent);
end;

function TEduMenuFrame.GetCleanCaption(Item: TIDEMenuItem): string;
var
  i: Integer;
begin
  Result:=Item.Caption;
  for i:=length(Result) downto 1 do
    if Result[i] in ['"','&'] then
      System.Delete(Result,i,1);
end;

function TEduMenuFrame.TVNodeToName(TVNode: TTreeNode): string;
var
  p: Integer;
begin
  Result:=TVNode.Text;
  p:=length(Result);
  if Result[p]='"' then begin
    dec(p);
    while (Result[p]<>'"') do dec(p);
    while (Result[p]<>':') do dec(p);
    Result:=copy(Result,1,p-1);
  end;
end;

function TEduMenuFrame.GetTitle: String;
begin
  Result:=ersEduMenuTitle
end;

procedure TEduMenuFrame.ReadSettings(AOptions: TAbstractIDEOptions);
begin
  if AOptions=EducationOptions then begin
    FillMenuTree;
  end;
end;

procedure TEduMenuFrame.Setup(ADialog: TAbstractOptionsEditorDialog);
begin
  ShowAllButton.Caption:=ersShowAll;
  ShowAllChildsButton.Caption:=ersShowAllChilds;
  MenusGroupBox.Caption:=ersIDEMenuItems;
end;

class function TEduMenuFrame.SupportedOptionsClass: TAbstractIDEOptionsClass;
begin
  Result:=EducationIDEOptionsClass;
end;

procedure TEduMenuFrame.WriteSettings(AOptions: TAbstractIDEOptions);
begin
  if AOptions=EducationOptions then begin
    SaveMenuTree;
  end;
end;

{ TEduMenuOptions }

function TEduMenuOptions.GetMenuHidden(MenuPath: string): boolean;
begin
  Result:=fHidden[MenuPath]='1';
end;

procedure TEduMenuOptions.SetMenuHidden(MenuPath: string; const AValue: boolean);
begin
  if AValue then
    fHidden[MenuPath]:='1'
  else
    fHidden.Remove(MenuPath);
end;

constructor TEduMenuOptions.Create;
begin
  inherited Create;
  Name:='Menus';
  fHidden:=TStringToStringTree.Create(false);
end;

destructor TEduMenuOptions.Destroy;
begin
  FreeAndNil(fHidden);
  inherited Destroy;
end;

procedure TEduMenuOptions.ClearHMenuidden;
begin
  fHidden.Clear;
end;

function TEduMenuOptions.Load(Config: TConfigStorage): TModalResult;
var
  Cnt: LongInt;
  i: Integer;
  MenuPath: String;
begin
  fHidden.Clear;
  Cnt:=Config.GetValue('Hidden/Count',0);
  for i:=1 to Cnt do begin
    MenuPath:=Config.GetValue('Hidden/Item'+IntToStr(i),'');
    if MenuPath='' then continue;
    fHidden[MenuPath]:='1';
  end;
  Result:=inherited Load(Config);
end;

function TEduMenuOptions.Save(Config: TConfigStorage): TModalResult;
var
  Node: TAvlTreeNode;
  Item: PStringToStringItem;
  Cnt: Integer;
begin
  Cnt:=0;
  Node:=fHidden.Tree.FindLowest;
  while Node<>nil do begin
    inc(Cnt);
    Item:=PStringToStringItem(Node.Data);
    Config.SetDeleteValue('Hidden/Item'+IntToStr(Cnt),Item^.Name,'');
    Node:=fHidden.Tree.FindSuccessor(Node);
  end;
  Config.SetDeleteValue('Hidden/Count',Cnt,0);
  Result:=inherited Save(Config);
end;

function TEduMenuOptions.MenuItemToPath(Item: TIDEMenuItem): string;
begin
  Result:='';
  while Item<>nil do begin
    if Result<>'' then
      Result:='/'+Result;
    Result:=Item.Name+Result;
    Item:=Item.Section;
  end;
end;

function TEduMenuOptions.FindItemWithPath(Path: string): TIDEMenuItem;
begin
  Result:=IDEMenuRoots.FindByPath(Path,false);
end;

function TEduMenuOptions.KeepItemVisible(Item: TIDEMenuItem): boolean;
begin
  if (Item=itmSecondaryTools) or (Item.HasAsParent(itmSecondaryTools)) then exit(true);
  Result:=false;
end;

procedure TEduMenuOptions.Apply(Enable: boolean);

  procedure ApplyRecursive(Item: TIDEMenuItem);
  var
    Section: TIDEMenuSection;
    i: Integer;
  begin
    if (not KeepItemVisible(Item)) then begin
      if Enable then begin
        if MenuHidden[MenuItemToPath(Item)] then
          Item.Visible:=false;
        // Note: do not show items. Some items should be hidden independent of education.
      end else begin
        if MenuHidden[MenuItemToPath(Item)] then
          Item.Visible:=true;
      end;
    end;
    if Item is TIDEMenuSection then begin
      Section:=TIDEMenuSection(Item);
      for i:=0 to Section.Count-1 do
        ApplyRecursive(Section[i]);
    end;
  end;

var
  i: Integer;
begin
  inherited Apply(Enable);
  for i:=0 to IDEMenuRoots.Count-1 do
    ApplyRecursive(IDEMenuRoots[i]);
end;

{$R *.lfm}

end.
