lazarus/designer/menushortcutdisplay.pas

511 lines
15 KiB
ObjectPascal

{***************************************************************************
* *
* 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.