{***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************}

unit MenuShortcutDisplay;

{$mode objfpc}{$H+}

interface

uses
  // FCL
  Classes, SysUtils,
  // LCL
  ButtonPanel, Controls, StdCtrls, Menus, Forms, LCLIntf, LCLProc,
  // LazUtils
  LazUTF8,
  // IdeIntf
  FormEditingIntf, PropEdits,
  // IDE
  LazarusIDEStrConsts, MenuDesignerBase, MenuShortcuts;

type

  { TShortcutDisplayDlg }

  TShortcutDisplayDlg = class(TForm)
  strict private
    FLastSortIndex: integer;
    FMenu: TMenu;
    FscList: TStringListUTF8Fast;
    FSingleMenuOnly: boolean;
    FShortcutsOnly: boolean;
    FShortcuts: TMenuShortcuts;
    FShadowMenu: TShadowMenuBase;
    // GUI
    FBPanel: TButtonPanel;
    FDualDisplay: TDualDisplay;
    FGBDisplay: TGroupBox;
    FLabel: TLabel;
    procedure AddSCitemToListRecursive(anItem: TMenuItem);
    procedure DisplayAllDlgClick(isHeader: boolean; index: integer);
    procedure DisplaySingleMenuClick(isHeader: boolean; index: integer);
    procedure UpdateContents(singleMenuOnly: boolean=False);
    procedure UpdateFromMenu(anIndex: integer= -1);
  public
    constructor CreateWithShortcutsOnly(aShortcuts: TMenuShortcuts; shortcutsOnly: boolean;
                                        aShadowMenu: TShadowMenuBase; aMenu: TMenu=nil);
    destructor Destroy; override;
  end;

  { TEditCaptionDialog }

  TEditCaptionDialog = class(TForm)
  strict private
    FButtonPanel: TButtonPanel;
    FEdit: TEdit;
    FGBEdit: TGroupBox;
    FMenuItem: TMenuItem;
    FNewShortcut: TShortCut;
    FOldShortcut: TShortCut;
    procedure EditOnChange(Sender: TObject);
    procedure OKButtonClick(Sender: TObject);
  public
    constructor CreateWithMenuItem(AOwner: TComponent; aMI: TMenuItem; aSC: TShortCut);
    property NewShortcut: TShortCut read FNewShortcut;
  end;

function ListShortCutDlg(aShortcuts: TMenuShortcuts; shortcutsOnly: boolean;
  aShadowMenu: TShadowMenuBase; aMenu: TMenu): TModalResult;
function EditCaptionDlg(aMI: TMenuItem; var aShortcut: TShortCut): boolean;

implementation

function ListShortCutDlg(aShortcuts: TMenuShortcuts; shortcutsOnly: boolean;
  aShadowMenu: TShadowMenuBase; aMenu: TMenu): TModalResult;
var
  dlg: TShortcutDisplayDlg;
begin
  dlg:=TShortcutDisplayDlg.CreateWithShortcutsOnly(aShortcuts, shortcutsOnly,
                                                   aShadowMenu, aMenu);
  try
    Result:=dlg.ShowModal;
  finally
    FreeAndNil(dlg);
  end;
end;

function EditCaptionDlg(aMI: TMenuItem; var aShortcut: TShortCut): boolean;
var
  dlg: TEditCaptionDialog;
begin
  dlg := TEditCaptionDialog.CreateWithMenuItem(nil, aMI, aShortcut);
  try
    Result := dlg.ShowModal = mrOK;
    if Result then
      aShortcut := dlg.NewShortcut;
  finally
    dlg.Free;
  end;
end;

{ TShortcutDisplayDlg }

constructor TShortcutDisplayDlg.CreateWithShortcutsOnly(aShortcuts: TMenuShortcuts;
  shortcutsOnly: boolean; aShadowMenu: TShadowMenuBase; aMenu: TMenu);
var
  s: string;
  lurStr: string;
begin
  inherited CreateNew(nil);
  FShortcutsOnly:=shortcutsOnly;
  FShortcuts:=aShortcuts;
  FMenu:=aMenu;
  FShadowMenu:=aShadowMenu;
  FSingleMenuOnly:=(FMenu <> nil);
  FLastSortIndex:= -1;
  if FSingleMenuOnly then
    Caption:=Format(lisMenuEditorShortcutsUsedInS, [FMenu.Name])
  else begin
    if shortcutsOnly then
      s:=lisMenuEditorSShortcuts
    else
      s:=lisMenuEditorSShortcutsAndAcceleratorKeys;
    lurStr:=TComponent(GlobalDesignHook.LookupRoot).Name;
    Caption:=Format(s, [lurStr]);
  end;

  BorderStyle:=bsDialog;
  Position:=poScreenCenter;
  Constraints.MinWidth:=460;
  Constraints.MaxHeight:=460;

  FBPanel:=TButtonPanel.Create(Self);
  with FBPanel do begin
    ShowBevel:=False;
    BorderSpacing.Around:=Spacing;
    BorderSpacing.Right:=Spacing;
    Constraints.MinHeight:=42;
    ShowButtons:=[pbClose];
    CloseButton.Constraints.MaxHeight:=30;
    Parent:=Self;
  end;

  FLabel:=TLabel.Create(Self);
  FLabel.WordWrap:=True;
  FLabel.Constraints.MaxWidth:=340;
  FLabel.BorderSpacing.Left:=Margin;
  FLabel.AutoSize:=True;
  FLabel.Parent:=FBPanel;

  FGBDisplay:=TGroupBox.Create(Self);
  with FGBDisplay do begin
    Align:=alClient;
    BorderSpacing.Around:=Margin*2;
    AutoSize:=True;
    Parent:=Self;
  end;

  FDualDisplay:=TDualDisplay.Create(Self);
  with FDualDisplay do begin
    Align:=alClient;
    BorderSpacing.Around:=Margin;
    Parent:=FGBDisplay;
    if FSingleMenuOnly then begin
      OnDisplayClick:=@DisplaySingleMenuClick;
      AddHeader(lisMenuEditorShortcutSourceProperty);
      Caption:=lisMenuEditorShortcuts;
    end
    else begin
      OnDisplayClick:=@DisplayAllDlgClick;
      if FShortcutsOnly then begin
        AddHeader(lisMenuEditorShortcutSourceProperty);
        Caption:=Format(lisMenuEditorSShortcuts, [lurStr]);
      end
      else begin
        AddHeader(lisMenuEditorShortcutSourceProperty);
        Caption:=Format(lisMenuEditorSShortcutsAndAcceleratorKeys, [lurStr]);
      end;
    end;
  end;
  UpdateContents(FSingleMenuOnly);
  FLabel.Caption:=lisMenuEditorClickANonGreyedItemToEditItsShortcut;
  AutoSize:=True;
end;

destructor TShortcutDisplayDlg.Destroy;
begin
  FreeAndNil(FscList);
  inherited Destroy;
end;

procedure TShortcutDisplayDlg.DisplaySingleMenuClick(isHeader: boolean; index: integer);
var
  mi: TMenuItem;
  sc: TShortCut;
  result: boolean;
  si: TShadowItemBase;
  info: TSCInfo;
  isMainSC: boolean;
begin
  case isHeader of

    True: begin // header click
      if (FLastSortIndex = index) or (FscList.Count = 0) then
        Exit;
      FLastSortIndex:=index;
      UpdateFromMenu(index);
    end;

    False: begin // contents click
      info:=TSCInfo(FscList.Objects[index]);
      mi:=info.MenuItem;
      if (mi <> nil) then
      begin
        sc:=info.Shortcut;
        isMainSC:=(info.Kind = scMenuItemSC);
        result:=AddNewOrEditShortcutDlg(mi, isMainSC, sc);
        if result then
        begin
          if isMainSC then
            mi.ShortCut:=sc
          else
            mi.ShortCutKey2:=sc;
          si:=FShadowMenu.GetShadowForMenuItem(mi);
          if (si <> nil) then begin
            FShadowMenu.UpdateBoxLocationsAndSizes;
            si.Repaint;
          end;
          FShortcuts.UpdateShortcutList(False);
          UpdateFromMenu;
          GlobalDesignHook.RefreshPropertyValues;
          GlobalDesignHook.Modified(mi);
          FShadowMenu.RefreshFakes;
        end;
      end;
    end;

  end; // case
end;

procedure TShortcutDisplayDlg.DisplayAllDlgClick(isHeader: boolean; index: integer);
var
  i: integer;
  dt: TDisplayType;
  inf: TSCInfo;
  mi: TMenuItem;
  sc: TShortCut;
  isMainSC, isCaptionSC, result: boolean;
  si: TShadowItemBase;
begin
  case isHeader of
    True: begin
      if (FLastSortIndex = index) or (FDualDisplay.ContentsCount = 0) then
        Exit;
      FLastSortIndex:=index;
      FDualDisplay.ClearContents;
      case index of
        0: FShortcuts.ShortcutList.ScanList.Sort;
        1: FShortcuts.ShortcutList.SortByComponentPropertyName;
      end;
      for i:=0 to FShortcuts.ShortcutList.ScanList.Count-1 do
        begin
          inf:=TSCInfo(FShortcuts.ShortcutList.ScanList.Objects[i]);
          if (inf.Kind in MenuItem_Kinds) then
            dt:=dtBlack
          else
            dt:=dtGreyed;
          FDualDisplay.AddLine(FShortcuts.ShortcutList.ScanList[i] + ',' +
                     inf.Component.Name+ '.' + KindToPropertyName(inf.Kind), dt);
        end;
    end;
    False: begin
      inf:=TSCInfo(FShortcuts.ShortcutList.ScanList.Objects[index]);
      mi:=inf.MenuItem;
      if (mi <> nil) then
        begin
          isMainSC:=(inf.Kind in ShortcutOnly_Kinds);
          isCaptionSC:=(inf.Kind in Accelerator_Kinds);
          sc:=inf.Shortcut;
          if isCaptionSC then
            result:=EditCaptionDlg(mi, sc)
          else result:=AddNewOrEditShortcutDlg(mi, isMainSC, sc);
          if result then
            begin
              if not isCaptionSC and isMainSC then
                mi.ShortCut:=sc
              else if not isCaptionSC then
                mi.ShortCutKey2:=sc;
              si:=FShadowMenu.GetShadowForMenuItem(mi);
              if (si <> nil) then
              begin
                FShadowMenu.UpdateBoxLocationsAndSizes;
                si.Repaint;
              end;
              FShortcuts.UpdateShortcutList(True);
              UpdateContents;
              GlobalDesignHook.RefreshPropertyValues;
              GlobalDesignHook.Modified(mi);
              FShadowMenu.RefreshFakes;
            end;
        end;
    end;
  end; // case
end;

procedure TShortcutDisplayDlg.UpdateContents(singleMenuOnly: boolean);
var
  i: integer;
  dt: TDisplayType;
  kind: TSCKind;
  inf: TSCInfo;
begin
  FDualDisplay.ClearContents;
  if singleMenuOnly then
    UpdateFromMenu
  else begin
    FShortcuts.UpdateShortcutList(not FShortcutsOnly);
    if (FShortcuts.ShortcutList.ScanList.Count = 0) then
      begin
        FDualDisplay.AddLine(lisMenuEditorNoneNone, dtGreyed);
        if FShortcutsOnly then
          FGBDisplay.Caption:=lisMenuEditorShortcuts
        else
          FGBDisplay.Caption:=lisMenuEditorShortcutsAndAcceleratorKeys;
      end
    else
      begin
        if FShortcutsOnly then
          FGBDisplay.Caption:=Format(lisMenuEditorShortcutsD,
            [FShortcuts.ShortcutList.ShortcutsInContainerCount])
        else
          FGBDisplay.Caption:=Format(lisMenuEditorShortcutsDAndAcceleratorKeysD,
            [FShortcuts.ShortcutList.ShortcutsInContainerCount,
             FShortcuts.ShortcutList.AcceleratorsInContainerCount]);

        FDualDisplay.BeginUpdate;
        for i:=0 to FShortcuts.ShortcutList.ScanList.Count-1 do
          begin
            inf:=TSCInfo(FShortcuts.ShortcutList.ScanList.Objects[i]);
            kind:=inf.Kind;
            if (kind in MenuItem_Kinds) then
              dt:=dtBlack
            else
              dt:=dtGreyed;
            FDualDisplay.AddLine(FShortcuts.ShortcutList.ScanList[i] + ',' +
                         inf.Component.Name + '.' + KindToPropertyName(inf.Kind), dt);
          end;
        FDualDisplay.EndUpdate;
      end;
  end;
end;

procedure TShortcutDisplayDlg.AddSCitemToListRecursive(anItem: TMenuItem);
var
  inf: TSCInfo;
  i: integer;
begin
  if (anItem.ShortCut <> 0) then begin
    inf:=TSCInfo.CreateWithParams(anItem, scMenuItemSC, anItem.ShortCut);
    FscList.AddObject(ShortCutToText(anItem.ShortCut), TObject(inf));
  end;
  if (anItem.ShortCutKey2 <> 0) and (anItem.ShortCutKey2 <> anItem.ShortCut) then begin
    inf:=TSCInfo.CreateWithParams(anItem, scMenuItemKey2, anItem.ShortCutKey2);
    FscList.AddObject(ShortCutToText(anItem.ShortCutKey2), TObject(inf));
  end;
  for i:=0 to anItem.Count-1 do
    AddSCitemToListRecursive(anItem[i]);
end;

procedure TShortcutDisplayDlg.UpdateFromMenu(anIndex: integer);
var
  i: integer;
  inf: TSCInfo;
begin
  Assert(FMenu<>nil,'TShortcutDisplayDlg.UpdateFromMenu: FMenu is nil');
  FreeAndNil(FscList);
  FscList:=TStringListUTF8Fast.Create;
  FscList.CaseSensitive:=False;
  FDualDisplay.ClearContents;
  for i:=0 to FMenu.Items.Count-1 do
    AddSCitemToListRecursive(FMenu.Items[i]);
  if (FscList.Count = 0) then
    begin
      FDualDisplay.AddLine(lisMenuEditorNoneNone, dtGreyed);
      FGBDisplay.Caption:=lisMenuEditorShortcuts;
    end
  else
    begin
      FGBDisplay.Caption := Format(lisMenuEditorShortcutsUsedInSD,
          [FMenu.Name, FscList.Count]);
      case anIndex of
        -1: ; // unsorted
        0: FscList.Sort;
        1: FscList.CustomSort(@SortByComponentPropertyName);
      end;
      FDualDisplay.BeginUpdate;
      for i:=0 to FscList.Count-1 do
        begin
          inf:=TSCInfo(FscList.Objects[i]);
          FDualDisplay.AddLine(FscList[i] + ',' +
            inf.Component.Name + '.' + KindToPropertyName(inf.Kind), dtBlack);
        end;
      FDualDisplay.EndUpdate;
      FDualDisplay.InvalidateContents;
    end;
end;

{ TEditCaptionDialog }

constructor TEditCaptionDialog.CreateWithMenuItem(AOwner: TComponent;
  aMI: TMenuItem; aSC: TShortCut);
var
  key: word;
  sstate: TShiftState;
  p: integer;
  ch: Char;
begin
  inherited CreateNew(AOwner);
  FMenuItem:=aMI;
  FOldShortcut:=aSC;
  ShortCutToKey(aSC, key, sstate);
  Position:=poScreenCenter;
  BorderStyle:=bsDialog;
  Caption:=Format(lisMenuEditorEditingCaptionOfS, [FMenuItem.Name]);

  FButtonPanel:=TButtonPanel.Create(Self);
  with FButtonPanel do
    begin
      ShowButtons:=[pbOK, pbCancel];
      OKButton.Name:='OKButton';
      OKButton.DefaultCaption:=True;
      OKButton.Enabled:=False;
      OKButton.OnClick:=@OKButtonClick;
      CancelButton.Name:='CancelButton';
      CancelButton.DefaultCaption:=True;
      ShowBevel:=False;
      Parent:=Self;
    end;

  FGBEdit:=TGroupBox.Create(Self);
  with FGBEdit do
    begin
      BorderSpacing.Around:=Margin;
      p:=UTF8Pos('&', aMI.Caption);
      if (p > 0) and (p < UTF8Length(aMI.Caption)) then
        ch:=aMI.Caption[Succ(p)] // gets correct case of key
      else
        ch:=Chr(Ord(key));    // fallback
    Caption:=Format(lisMenuEditorAcceleratorKeySNeedsChanging, [ch]);
      Align:=alClient;
      Parent:=Self;
    end;

  FEdit:=TEdit.Create(Self);
  with FEdit do
    begin;
      BorderSpacing.Around:=Margin;
      Text:=FMenuItem.Caption;
      Align:=alClient;
      OnChange:=@EditOnChange;
      Parent:=FGBEdit;
    end;

  AutoSize:=True;
end;

procedure TEditCaptionDialog.EditOnChange(Sender: TObject);
var
  hasAccel: boolean;
  sc: TShortCut;
begin
  if (FEdit.Text = '') then
    begin
      FEdit.Text:=lisMenuEditorCaptionShouldNotBeBlank;
      FEdit.SetFocus;
    end
  else
  begin
    hasAccel:=HasAccelerator(FEdit.Text, sc);
    if (not hasAccel) or (hasAccel and (sc <> FOldShortcut)) then
    begin
      FNewShortcut:=sc;
      FButtonPanel.OKButton.Enabled:=True;
    end;
  end;
end;

procedure TEditCaptionDialog.OKButtonClick(Sender: TObject);
begin
  FMenuItem.Caption:=FEdit.Text;
end;

end.

