{ *************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** } unit editor_keymapping_options; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, TreeFilterEdit, Forms, StdCtrls, ComCtrls, Controls, Dialogs, LCLType, LCLProc, Menus, Buttons, Clipbrd, EditorOptions, LazarusIDEStrConsts, IDEOptionsIntf, IDEImagesIntf, editor_general_options, KeymapSchemeDlg, KeyMapping, IDECommands, KeyMapShortCutDlg, SrcEditorIntf, EditBtn; type { TEditorKeymappingOptionsFrame } TEditorKeymappingOptionsFrame = class(TAbstractIDEOptionsEditor) ChooseSchemeButton: TBitBtn; ClearButton: TBitBtn; EditButton: TBitBtn; ConsistencyCheckButton: TBitBtn; FilterEdit: TTreeFilterEdit; FindKeyButton: TBitBtn; CommandLabel: TLabel; SchemeLabel: TLabel; ResetKeyFilterBtn: TSpeedButton; TreeView: TTreeView; EditMenuItem: TMenuItem; ClearMenuItem: TMenuItem; PopupMenu1: TPopupMenu; procedure ClearMenuItemClick(Sender: TObject); procedure EditButtonClick(Sender: TObject); procedure EditMenuItemClick(Sender: TObject); procedure ChooseSchemeButtonClick(Sender: TObject); procedure ClearButtonClick(Sender: TObject); procedure ConsistencyCheckButtonClick(Sender: TObject); function FilterEditFilterItem(Item: TObject; out Done: Boolean): Boolean; procedure FilterEditKeyPress(Sender: TObject; var Key: char); procedure FindKeyButtonClick(Sender: TObject); procedure ResetKeyFilterBtnClick(Sender: TObject); procedure TreeViewDblClick(Sender: TObject); procedure TreeViewKeyPress(Sender: TObject; var Key: char); procedure TreeViewSelectionChanged(Sender: TObject); procedure PopupMenu1Popup(Sender: TObject); private FDialog: TAbstractOptionsEditorDialog; EditingKeyMap: TKeyCommandRelationList; KeyMapKeyFilter: TIDEShortCut; fModified: Boolean; function GeneralPage: TEditorGeneralOptionsFrame; inline; procedure FillKeyMappingTreeView; procedure EditCommandMapping(ANode: TTreeNode); procedure ClearCommandMapping(ANode: TTreeNode); function KeyMappingRelationToString(Index: Integer): String; function KeyMappingRelationToString(KeyRelation: TKeyCommandRelation): String; procedure UpdateKeyFilterButton; procedure UpdateSchemeLabel; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetTitle: String; override; procedure Setup(ADialog: TAbstractOptionsEditorDialog); override; procedure ReadSettings(AOptions: TAbstractIDEOptions); override; procedure WriteSettings(AOptions: TAbstractIDEOptions); override; class function SupportedOptionsClass: TAbstractIDEOptionsClass; override; end; implementation {$R *.lfm} var imgKeyCategory, imgKeyItem: Integer; type { TKeyMapErrorsForm } TKeyMapErrorsForm = class(TForm) ListBox: TListBox; BackButton: TButton; ErrorsPopupMenu: TPopupMenu; CopyMenuItem: TMenuItem; procedure BackButtonClick(Sender: TObject); procedure CopyMenuItemClick(Sender: TObject); public constructor Create(AnOwner: TComponent); override; end; constructor TKeyMapErrorsForm.Create(AnOwner: TComponent); begin inherited CreateNew(AnOwner); SetBounds((Screen.Width - 410) div 2, (Screen.Height - 260) div 2, 400, 250); Caption := dlgKeyMappingErrors; ListBox := TListBox.Create(Self); with ListBox do begin Name := 'ListBox'; Align := alTop; Parent := Self; end; BackButton := TButton.Create(Self); with BackButton do begin Name := 'BackButton'; AutoSize := true; Anchors := [akLeft,akBottom]; Parent := Self; AnchorParallel(akBottom,6,Self); AnchorParallel(akLeft,6,Self); Caption := dlgEdBack; OnClick := @BackButtonClick; end; ErrorsPopupMenu := TPopupMenu.Create(Self); ErrorsPopupMenu.Name := 'ErrorsPopupMenu'; CopyMenuItem := TMenuItem.Create(Self); CopyMenuItem.Caption := lisCopyAllItemsToClipboard; CopyMenuItem.OnClick := @CopyMenuItemClick; CopyMenuItem.ImageIndex := IDEImages.LoadImage(16, 'laz_copy'); ErrorsPopupMenu.Items.Add(CopyMenuItem); ListBox.AnchorToNeighbour(akBottom,6,BackButton); ListBox.PopupMenu := ErrorsPopupMenu; end; procedure TKeyMapErrorsForm.BackButtonClick(Sender: TObject); begin ModalResult := mrOk; end; procedure TKeyMapErrorsForm.CopyMenuItemClick(Sender: TObject); begin Clipboard.AsText := ListBox.Items.Text; end; { TEditorKeymappingOptionsFrame } constructor TEditorKeymappingOptionsFrame.Create(AOwner: TComponent); begin inherited Create(AOwner); EditingKeyMap := TKeyCommandRelationList.Create; EditButton.Enabled:=false; ClearButton.Enabled:=false; fModified:=False; end; destructor TEditorKeymappingOptionsFrame.Destroy; begin EditingKeyMap.Free; inherited Destroy; end; procedure TEditorKeymappingOptionsFrame.ChooseSchemeButtonClick(Sender: TObject); var NewScheme: String; begin NewScheme := EditorOpts.KeyMappingScheme; if ShowChooseKeySchemeDialog(NewScheme) = mrOk then begin EditorOpts.KeyMappingScheme := NewScheme; EditingKeyMap.LoadScheme(NewScheme); FillKeyMappingTreeView; fModified:=False; UpdateSchemeLabel; end; end; procedure TEditorKeymappingOptionsFrame.EditButtonClick(Sender: TObject); begin EditCommandMapping(TreeView.Selected); end; procedure TEditorKeymappingOptionsFrame.ClearButtonClick(Sender: TObject); begin ClearCommandMapping(TreeView.Selected); end; procedure TEditorKeymappingOptionsFrame.ConsistencyCheckButtonClick(Sender: TObject); var Protocol: TStringList; ErrorCount, Index1, Index2: Integer; ACaption, AText: String; KeyMapErrorsForm: TKeyMapErrorsForm; begin Protocol := TStringList.Create; try ErrorCount := FindKeymapConflicts(EditingKeyMap, Protocol, Index1, Index2); if ErrorCount > 0 then begin KeyMapErrorsForm := TKeyMapErrorsForm.Create(nil); try KeyMapErrorsForm.ListBox.Items.Assign(Protocol); KeyMapErrorsForm.ShowModal; finally KeyMapErrorsForm.Free; end; end else begin ACaption := dlgReport; AText := dlgEdNoErr; MessageDlg(ACaption, AText, mtInformation, [mbOk], 0); end; finally Protocol.Free; end; end; function TEditorKeymappingOptionsFrame.FilterEditFilterItem(Item: TObject; out Done: Boolean): Boolean; var KeyRel: TKeyCommandRelation; begin Done:=True; Result:=False; if Item is TKeyCommandRelation then begin KeyRel:=TKeyCommandRelation(Item); // Tree item is actual key command. Done:=False; Result:=KeyMapKeyFilter.Key1<>VK_UNKNOWN; if Result then begin // Key filter is defined Done:=True; Result:=(CompareIDEShortCutKey1s(@KeyMapKeyFilter,@KeyRel.ShortcutA)=0) or (CompareIDEShortCutKey1s(@KeyMapKeyFilter,@KeyRel.ShortcutB)=0); end; end; end; procedure TEditorKeymappingOptionsFrame.FilterEditKeyPress(Sender: TObject; var Key: char); begin ResetKeyFilterBtnClick(Nil); end; procedure TEditorKeymappingOptionsFrame.FindKeyButtonClick(Sender: TObject); var KeyFilter: TIDEShortCut; begin if ShowKeyMappingGrabForm(KeyFilter) = mrOK then begin KeyMapKeyFilter := KeyFilter; UpdateKeyFilterButton; FilterEdit.Filter:=''; // Allow only one of the filters to be active. FilterEdit.InvalidateFilter; end; end; procedure TEditorKeymappingOptionsFrame.ResetKeyFilterBtnClick(Sender: TObject); begin KeyMapKeyFilter.Key1 := VK_UNKNOWN; KeyMapKeyFilter.Key2 := VK_UNKNOWN; UpdateKeyFilterButton; // Allow only one of the filters to be active. FilterEdit.InvalidateFilter; end; procedure TEditorKeymappingOptionsFrame.TreeViewDblClick(Sender: TObject); var P: TPoint; ANode: TTreeNode; begin P := TreeView.ScreenToClient(Mouse.CursorPos); ANode := TreeView.GetNodeAt(P.X, P.Y); if (ANode<>nil) and (ANode.Data<>nil) and (TObject(ANode.Data) is TKeyCommandRelation) then EditCommandMapping(ANode); end; procedure TEditorKeymappingOptionsFrame.TreeViewKeyPress(Sender: TObject; var Key: char); begin if (Key = char(VK_RETURN)) and (TreeView.Selected<>nil) then EditCommandMapping(TreeView.Selected); end; procedure TEditorKeymappingOptionsFrame.TreeViewSelectionChanged(Sender: TObject); var ANode: TTreeNode; begin ANode := TreeView.Selected; EditButton.Enabled:= (ANode<>nil) and (ANode.Data<>nil) and (TObject(ANode.Data) is TKeyCommandRelation); ClearButton.Enabled:=EditButton.Enabled; end; procedure TEditorKeymappingOptionsFrame.PopupMenu1Popup(Sender: TObject); var ANode: TTreeNode; begin ANode := TreeView.Selected; EditMenuItem.Enabled:= (ANode<>nil) and (ANode.Data<>nil) and (TObject(ANode.Data) is TKeyCommandRelation); ClearMenuItem.Enabled := EditMenuItem.Enabled; end; procedure TEditorKeymappingOptionsFrame.EditMenuItemClick(Sender: TObject); begin EditCommandMapping(TreeView.Selected); end; procedure TEditorKeymappingOptionsFrame.ClearMenuItemClick(Sender: TObject); begin ClearCommandMapping(TreeView.Selected); end; function TEditorKeymappingOptionsFrame.GeneralPage: TEditorGeneralOptionsFrame; inline; begin Result := TEditorGeneralOptionsFrame(FDialog.FindEditor(TEditorGeneralOptionsFrame)); end; function TEditorKeymappingOptionsFrame.GetTitle: String; begin Result := dlgKeyMapping; end; procedure TEditorKeymappingOptionsFrame.Setup(ADialog: TAbstractOptionsEditorDialog); begin FDialog := ADialog; ChooseSchemeButton.Caption := lisEdOptsLoadAScheme; ConsistencyCheckButton.Caption := dlgCheckConsistency; FindKeyButton.Caption := lisFindKeyCombination; CommandLabel.Caption := lisSelectedCommandsMapping; EditButton.Caption := lisEdit; ClearButton.Caption := lisClear; EditMenuItem.Caption := lisEdit; ClearMenuItem.Caption := lisClear; TreeView.Images := IDEImages.Images_16; imgKeyCategory := IDEImages.LoadImage(16, 'item_keyboard'); imgKeyItem := IDEImages.LoadImage(16, 'item_character'); ChooseSchemeButton.LoadGlyphFromLazarusResource('item_keyboard'); // keymapcategory ConsistencyCheckButton.LoadGlyphFromLazarusResource('menu_tool_check_lfm'); FindKeyButton.LoadGlyphFromLazarusResource('menu_search_find'); EditButton.LoadGlyphFromLazarusResource('laz_edit'); ClearButton.LoadGlyphFromLazarusResource('menu_clean'); PopupMenu1.Images := IDEImages.Images_16; EditMenuItem.ImageIndex := IDEImages.LoadImage(16, 'laz_edit'); ClearMenuItem.ImageIndex := IDEImages.LoadImage(16, 'menu_clean'); ResetKeyFilterBtn.LoadGlyphFromLazarusResource(ResBtnListFilter); ResetKeyFilterBtn.Enabled := not IDEShortCutEmpty(KeyMapKeyFilter); // FillKeyMappingTreeView; ... Done in ReadSettings. // UpdateSchemeLabel; end; procedure TEditorKeymappingOptionsFrame.ReadSettings(AOptions: TAbstractIDEOptions); var i: integer; begin with AOptions as TEditorOptions do EditingKeyMap.Assign(KeyMap); FillKeyMappingTreeView; UpdateSchemeLabel; with GeneralPage do for i := Low(PreviewEdits) to High(PreviewEdits) do if PreviewEdits[i] <> nil then EditingKeyMap.AssignTo(PreviewEdits[i].KeyStrokes, TSourceEditorWindowInterface); end; procedure TEditorKeymappingOptionsFrame.WriteSettings(AOptions: TAbstractIDEOptions); begin with AOptions as TEditorOptions do KeyMap.Assign(EditingKeyMap); end; class function TEditorKeymappingOptionsFrame.SupportedOptionsClass: TAbstractIDEOptionsClass; begin Result := TEditorOptions; end; procedure TEditorKeymappingOptionsFrame.FillKeyMappingTreeView; var i, j: Integer; NewCategoryNode, NewKeyNode: TTreeNode; CurCategory: TIDECommandCategory; CurKeyRelation: TKeyCommandRelation; ChildNodeIndex: Integer; CategoryNodeIndex: Integer; ItemCaption: String; begin with TreeView do begin BeginUpdate; CategoryNodeIndex:=0; for i := 0 to EditingKeyMap.CategoryCount - 1 do begin CurCategory := EditingKeyMap.Categories[i]; if Items.TopLvlCount > CategoryNodeIndex then begin NewCategoryNode := Items.TopLvlItems[CategoryNodeIndex]; NewCategoryNode.Text := CurCategory.Description; NewCategoryNode.Data := CurCategory; end else NewCategoryNode := Items.AddObject(Nil, CurCategory.Description, CurCategory); NewCategoryNode.ImageIndex := imgKeyCategory; NewCategoryNode.SelectedIndex := NewCategoryNode.ImageIndex; ChildNodeIndex:=0; for j := 0 to CurCategory.Count - 1 do begin CurKeyRelation := TKeyCommandRelation(CurCategory[j]); ItemCaption:=KeyMappingRelationToString(CurKeyRelation); if NewCategoryNode.Count > ChildNodeIndex then begin NewKeyNode := NewCategoryNode.Items[ChildNodeIndex]; NewKeyNode.Text := ItemCaption; NewKeyNode.Data := CurKeyRelation; end else NewKeyNode := Items.AddChildObject(NewCategoryNode,ItemCaption, CurKeyRelation); NewKeyNode.ImageIndex := imgKeyItem; NewKeyNode.SelectedIndex := NewKeyNode.ImageIndex; inc(ChildNodeIndex); end; // delete unneeded ones while NewCategoryNode.Count > ChildNodeIndex do NewCategoryNode[NewCategoryNode.Count - 1].Delete; if NewCategoryNode.Count>0 then inc(CategoryNodeIndex); end; while Items.TopLvlCount > CategoryNodeIndex do Items.TopLvlItems[Items.TopLvlCount - 1].Delete; EndUpdate; end; end; function TEditorKeymappingOptionsFrame.KeyMappingRelationToString(Index: Integer): String; begin Result := KeyMappingRelationToString(EditingKeyMap.Relations[Index]); end; function TEditorKeymappingOptionsFrame.KeyMappingRelationToString( KeyRelation: TKeyCommandRelation): String; const MaxLength = 60; function AddBrakets(S: String): String; begin Result := '[' + S + ']'; end; begin with KeyRelation do begin Result := LocalizedName; if UTF8Length(Result)>MaxLength then Result := UTF8Copy(LocalizedName, 1, MaxLength)+'...'; if Result <> '' then Result := Result + ' '; if (ShortcutA.Key1 = VK_UNKNOWN) and (ShortcutB.Key1 = VK_UNKNOWN) then Result := Result{ + lisNone2 } else if (ShortcutA.Key1 = VK_UNKNOWN) then Result := Result + AddBrakets(KeyAndShiftStateToEditorKeyString(ShortcutB)) else if (ShortcutB.Key1 = VK_UNKNOWN) then Result := Result + AddBrakets(KeyAndShiftStateToEditorKeyString(ShortcutA)) else Result := Result + AddBrakets(KeyAndShiftStateToEditorKeyString(ShortcutA)) + ' '+lisOr+' ' + AddBrakets(KeyAndShiftStateToEditorKeyString(ShortcutB)); end; end; procedure TEditorKeymappingOptionsFrame.UpdateKeyFilterButton; begin if IDEShortCutEmpty(KeyMapKeyFilter) then FindKeyButton.Caption:=lisFindKeyCombination else FindKeyButton.Caption:= Format(lisFilter3, [KeyAndShiftStateToEditorKeyString(KeyMapKeyFilter)]); ResetKeyFilterBtn.Enabled := not IDEShortCutEmpty(KeyMapKeyFilter); end; procedure TEditorKeymappingOptionsFrame.UpdateSchemeLabel; var s: String; begin s:=lisNowLoadedScheme+EditorOpts.KeyMappingScheme; if fModified then s:=s+' (*)'; SchemeLabel.Caption:=s; end; procedure TEditorKeymappingOptionsFrame.EditCommandMapping(ANode: TTreeNode); var i: integer; ARelation: TKeyCommandRelation; begin ARelation := TKeyCommandRelation(ANode.Data); i := EditingKeyMap.IndexOf(ARelation); if (i >= 0) and (ShowKeyMappingEditForm(i, EditingKeyMap) = mrOk) then begin FillKeyMappingTreeView; fModified:=True; UpdateSchemeLabel; with GeneralPage do for i := Low(PreviewEdits) to High(PreviewEdits) do if PreviewEdits[i] <> nil then EditingKeyMap.AssignTo(PreviewEdits[i].KeyStrokes, TSourceEditorWindowInterface); end; end; procedure TEditorKeymappingOptionsFrame.ClearCommandMapping(ANode: TTreeNode); var i: integer; ARelation: TKeyCommandRelation; begin ARelation := TKeyCommandRelation(ANode.Data); i := EditingKeyMap.IndexOf(ARelation); if (i >= 0) {and (ShowKeyMappingEditForm(i, EditingKeyMap) = mrOk)} then begin ARelation.ShortcutA := IDEShortCut(VK_UNKNOWN, []); ARelation.ShortcutB := IDEShortCut(VK_UNKNOWN, []); FillKeyMappingTreeView; fModified:=True; UpdateSchemeLabel; with GeneralPage do for i := Low(PreviewEdits) to High(PreviewEdits) do if PreviewEdits[i] <> nil then EditingKeyMap.AssignTo(PreviewEdits[i].KeyStrokes, TSourceEditorWindowInterface); end; end; initialization RegisterIDEOptionsEditor(GroupEditor, TEditorKeymappingOptionsFrame, EdtOptionsKeys); end.