mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 01:08:12 +02:00
501 lines
15 KiB
ObjectPascal
501 lines
15 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
taborderdlg.pas
|
|
---------------
|
|
|
|
***************************************************************************/
|
|
|
|
***************************************************************************
|
|
* *
|
|
* 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 TabOrderDlg;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, AVL_Tree,
|
|
// LCL
|
|
Forms, Controls, Dialogs, Buttons, ExtCtrls, StdCtrls, ComCtrls, LCLType,
|
|
// LazUtils
|
|
LazLoggerBase,
|
|
// IdeIntf
|
|
PropEdits, IDEDialogs, IDEImagesIntf,
|
|
//IDE
|
|
IDEOptionDefs, LazarusIDEStrConsts;
|
|
|
|
type
|
|
|
|
{ TTabOrderDialog }
|
|
|
|
TTabOrderDialog = class(TForm)
|
|
ArrowDown: TSpeedButton;
|
|
ArrowUp: TSpeedButton;
|
|
CheckBoxSortRecursivly: TCheckBox;
|
|
PanelSortByPosition: TPanel;
|
|
ItemTreeview: TTreeView;
|
|
SortByPositionButton: TBitBtn;
|
|
procedure ItemTreeviewSelectionChanged(Sender: TObject);
|
|
procedure SortByPositionButtonClick(Sender: TObject);
|
|
procedure FormShow(Sender: TObject);
|
|
procedure TabOrderDialogCreate(Sender: TObject);
|
|
procedure UpSpeedbuttonClick(Sender: TObject);
|
|
procedure DownSpeedbuttonClick(Sender: TObject);
|
|
procedure ItemTreeviewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
private
|
|
FUpdating: Boolean;
|
|
procedure SwapNodes(ANode1, ANode2, NewSelected: TTreeNode);
|
|
procedure CheckButtonsEnabled;
|
|
procedure CreateCandidates(OwnerComponent: TComponent; Candidates: TAvlTree);
|
|
procedure CreateNodes(ParentControl: TWinControl; ParentNode: TTreeNode;
|
|
Candidates: TAvlTree);
|
|
procedure RefreshTree;
|
|
procedure SomethingChanged;
|
|
procedure PersistentAdded({%H-}APersistent: TPersistent; {%H-}Select: boolean);
|
|
procedure PersistentDeleting({%H-}APersistent: TPersistent);
|
|
procedure DeletePersistent(var {%H-}APersistent: TPersistent);
|
|
procedure SetSelection(const ASelection: TPersistentSelectionList);
|
|
procedure ComponentRenamed({%H-}AComponent: TComponent);
|
|
end;
|
|
|
|
{ TTabOrderPropEditor }
|
|
|
|
TTabOrderPropEditor = class(TIntegerPropertyEditor)
|
|
public
|
|
function GetAttributes: TPropertyAttributes; override;
|
|
procedure Edit; override;
|
|
end;
|
|
|
|
var
|
|
TabOrderDialog: TTabOrderDialog;
|
|
ShowTabOrderEditor: TNotifyEvent;
|
|
|
|
implementation
|
|
|
|
{$R *.lfm}
|
|
|
|
function SortNodeByControlPos(Item1, Item2: Pointer): Integer;
|
|
var
|
|
Node1: TTreeNode absolute Item1;
|
|
Node2: TTreeNode absolute Item2;
|
|
Control1, Control2: TControl;
|
|
HorzDiff, TopDiff: Integer;
|
|
begin
|
|
Control1 := TControl(Node1.Data);
|
|
Control2 := TControl(Node2.Data);
|
|
Assert(Control1.Parent = Control2.Parent);
|
|
HorzDiff := Control1.Left - Control2.Left;
|
|
if Control1.Parent.BiDiMode <> bdLeftToRight then
|
|
begin
|
|
Inc(HorzDiff, Control1.Width);
|
|
Dec(HorzDiff, Control2.Width);
|
|
HorzDiff := -HorzDiff;
|
|
end;
|
|
TopDiff := Control1.Top - Control2.Top;
|
|
if TopDiff = 0 then
|
|
Exit(HorzDiff);
|
|
|
|
// Control1/2 will now refer to upper and lower control.
|
|
if TopDiff > 0 then
|
|
begin
|
|
Control1 := TControl(Node2.Data);
|
|
Control2 := TControl(Node1.Data);
|
|
end;
|
|
// If a control is "almost completely" above the other, it takes precedence
|
|
//regardless of the horizontal positioning.
|
|
if (Control1.Top + Control1.Height div 2 < Control2.Top)
|
|
and (Control1.Top + Control1.Height < Control2.Top + Control2.Height)
|
|
then
|
|
Result := TopDiff
|
|
else
|
|
Result := HorzDiff;
|
|
end;
|
|
|
|
{ TTabOrderDialog }
|
|
|
|
procedure TTabOrderDialog.TabOrderDialogCreate(Sender: TObject);
|
|
begin
|
|
Name := NonModalIDEWindowNames[nmiwTabOrderEditor];
|
|
|
|
GlobalDesignHook.AddHandlerChangeLookupRoot(@SomethingChanged);
|
|
GlobalDesignHook.AddHandlerRefreshPropertyValues(@SomethingChanged);
|
|
GlobalDesignHook.AddHandlerPersistentAdded(@PersistentAdded);
|
|
GlobalDesignHook.AddHandlerPersistentDeleting(@PersistentDeleting);
|
|
GlobalDesignHook.AddHandlerDeletePersistent(@DeletePersistent);
|
|
GlobalDesignHook.AddHandlerSetSelection(@SetSelection);
|
|
GlobalDesignHook.AddHandlerComponentRenamed(@ComponentRenamed);
|
|
|
|
IDEImages.AssignImage(ArrowDown, 'arrow_down');
|
|
IDEImages.AssignImage(ArrowUp, 'arrow_up');
|
|
IDEImages.AssignImage(SortByPositionButton, 'menu_edit_sort');
|
|
|
|
ArrowDown.Hint:=lisTabOrderDownHint;
|
|
ArrowUp.Hint:=lisTabOrderUpHint;
|
|
SortByPositionButton.Hint:=lisTabOrderSortHint;
|
|
CheckBoxSortRecursivly.Caption := lisTabOrderRecursively;
|
|
CheckBoxSortRecursivly.Hint := lisTabOrderRecursionHint;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.FormShow(Sender: TObject);
|
|
var
|
|
Sel: TPersistentSelectionList;
|
|
begin
|
|
RefreshTree;
|
|
Sel := TPersistentSelectionList.Create;
|
|
try
|
|
GlobalDesignHook.GetSelection(Sel);
|
|
SetSelection(Sel);
|
|
finally
|
|
Sel.Free;
|
|
end;
|
|
CheckButtonsEnabled;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.SortByPositionButtonClick(Sender: TObject);
|
|
|
|
procedure SortControls(FirstItem: TTreeNode);
|
|
var
|
|
SortedNodes: TFPList;
|
|
I: Integer;
|
|
CurrItem: TTreeNode;
|
|
begin
|
|
if Assigned(FirstItem) then begin
|
|
SortedNodes := TFPList.Create;
|
|
try
|
|
CurrItem := FirstItem;
|
|
repeat
|
|
if CheckBoxSortRecursivly.Checked then
|
|
SortControls(CurrItem.GetFirstChild);
|
|
|
|
SortedNodes.Add(CurrItem);
|
|
CurrItem := CurrItem.GetNextSibling;
|
|
until CurrItem = nil;
|
|
|
|
SortedNodes.Sort(@SortNodeByControlPos);
|
|
|
|
for I := SortedNodes.Count - 1 downto 0 do
|
|
begin
|
|
CurrItem := TTreeNode(SortedNodes[I]);
|
|
CurrItem.MoveTo(FirstItem, naAddFirst);
|
|
TWinControl(CurrItem.Data).TabOrder := I;
|
|
CurrItem.Text := TWinControl(CurrItem.Data).Name + ' (' + IntToStr(I) + ')';
|
|
end;
|
|
finally
|
|
SortedNodes.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
FirstItem: TTreeNode;
|
|
begin
|
|
if (ItemTreeview.Selected <> nil) and (ItemTreeview.Selected.Parent <> nil) then
|
|
FirstItem := ItemTreeview.Selected.Parent.GetFirstChild
|
|
else
|
|
FirstItem := ItemTreeview.Items.GetFirstNode;
|
|
|
|
if IDEMessageDialog('', Format(lisTabOrderConfirmSort, [TControl(FirstItem.Data).Parent.Name]),
|
|
mtConfirmation, mbOKCancel) <> mrOK then
|
|
begin
|
|
Exit;
|
|
end;
|
|
|
|
ItemTreeview.BeginUpdate;
|
|
try
|
|
SortControls(FirstItem);
|
|
finally
|
|
ItemTreeview.EndUpdate;
|
|
end;
|
|
GlobalDesignHook.Modified(Self);
|
|
GlobalDesignHook.RefreshPropertyValues;
|
|
CheckButtonsEnabled;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.ItemTreeviewSelectionChanged(Sender: TObject);
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
if FUpdating then Exit;
|
|
Node := ItemTreeview.Selected;
|
|
if Assigned(Node) then begin
|
|
FUpdating := True;
|
|
GlobalDesignHook.SelectOnlyThis(TPersistent(Node.Data));
|
|
FUpdating := False;
|
|
CheckButtonsEnabled;
|
|
end;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.UpSpeedbuttonClick(Sender: TObject);
|
|
var
|
|
CurItem, NewItem: TTreeNode;
|
|
begin
|
|
CurItem := ItemTreeview.Selected;
|
|
if (CurItem=nil) or (CurItem.GetPrevSibling=nil) then exit;
|
|
NewItem := CurItem.GetPrevSibling;
|
|
SwapNodes(NewItem, CurItem, CurItem);
|
|
end;
|
|
|
|
procedure TTabOrderDialog.DownSpeedbuttonClick(Sender: TObject);
|
|
var
|
|
CurItem, NewItem: TTreeNode;
|
|
begin
|
|
CurItem:=ItemTreeview.Selected;
|
|
if (CurItem=nil) or (CurItem.GetNextSibling=nil) then exit;
|
|
NewItem := CurItem.GetNextSibling;
|
|
SwapNodes(CurItem, NewItem, CurItem);
|
|
end;
|
|
|
|
procedure TTabOrderDialog.ItemTreeviewKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
begin
|
|
if Shift = [ssCtrl] then
|
|
begin
|
|
case Key of
|
|
VK_UP:
|
|
begin
|
|
ArrowUp.Click;
|
|
Key := 0;
|
|
end;
|
|
VK_DOWN:
|
|
begin
|
|
ArrowDown.Click;
|
|
Key := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.SwapNodes(ANode1, ANode2, NewSelected: TTreeNode);
|
|
var
|
|
Ctrl1, Ctrl2: TWinControl;
|
|
TabOrd: TTabOrder;
|
|
begin
|
|
if IsVisible and not FUpdating then
|
|
begin
|
|
FUpdating := true;
|
|
ItemTreeview.BeginUpdate;
|
|
try
|
|
ANode2.MoveTo(ANode1,naInsert); // Move Node2 in front of Node1.
|
|
Ctrl1 := TWinControl(ANode1.Data);
|
|
Ctrl2 := TWinControl(ANode2.Data);
|
|
TabOrd := Ctrl1.TabOrder; // Swap TabOrder values.
|
|
Ctrl1.TabOrder := Ctrl2.TabOrder;
|
|
Ctrl2.TabOrder := TabOrd;
|
|
ANode1.Text := Ctrl1.Name + ' (' + IntToStr(Ctrl1.TabOrder) + ')';
|
|
ANode2.Text := Ctrl2.Name + ' (' + IntToStr(Ctrl2.TabOrder) + ')';
|
|
ItemTreeview.Selected := NewSelected;
|
|
GlobalDesignHook.Modified(Self);
|
|
GlobalDesignHook.RefreshPropertyValues;
|
|
CheckButtonsEnabled;
|
|
finally
|
|
ItemTreeview.EndUpdate;
|
|
FUpdating := false;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.CheckButtonsEnabled;
|
|
var
|
|
CurItem: TTreeNode;
|
|
begin
|
|
CurItem := ItemTreeview.Selected;
|
|
ArrowUp.Enabled := Assigned(CurItem) and Assigned(CurItem.GetPrevSibling);
|
|
ArrowDown.Enabled := Assigned(CurItem) and Assigned(CurItem.GetNextSibling);
|
|
SortByPositionButton.Enabled := Assigned(ItemTreeview.Items.GetFirstNode);
|
|
end;
|
|
|
|
procedure TTabOrderDialog.CreateCandidates(OwnerComponent: TComponent;
|
|
Candidates: TAvlTree);
|
|
var
|
|
i: Integer;
|
|
AComponent: TComponent;
|
|
begin
|
|
if OwnerComponent = nil then Exit;
|
|
if csDestroying in OwnerComponent.ComponentState then exit;
|
|
for i := 0 to OwnerComponent.ComponentCount - 1 do
|
|
begin
|
|
AComponent := OwnerComponent.Components[i];
|
|
if csDestroying in AComponent.ComponentState then continue;
|
|
if Candidates.Find(AComponent)<>nil then
|
|
begin
|
|
DebugLn('WARNING: TTabOrderDialog.CreateCandidates doppelganger found ', AComponent.Name);
|
|
end
|
|
else
|
|
begin
|
|
Candidates.Add(AComponent);
|
|
if csInline in AComponent.ComponentState then
|
|
CreateCandidates(AComponent, Candidates);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.CreateNodes(ParentControl: TWinControl; ParentNode: TTreeNode; Candidates: TAvlTree);
|
|
// Add all controls in Designer to ItemTreeview.
|
|
var
|
|
AControl: TControl;
|
|
i, CurTab: integer;
|
|
FirstSibling: TTreeNode;
|
|
NodeBehind: TTreeNode;
|
|
NewNode: TTreeNode;
|
|
NodeText: string;
|
|
AWinControl: TWinControl;
|
|
begin
|
|
ItemTreeview.BeginUpdate;
|
|
if ParentNode = nil then
|
|
FirstSibling := nil
|
|
else
|
|
FirstSibling := ParentNode.GetFirstChild;
|
|
for i := 0 to ParentControl.ControlCount - 1 do
|
|
begin
|
|
AControl := ParentControl.Controls[i];
|
|
// skip non TWinControls and invisible form designer controls
|
|
if not (AControl is TWinControl) or (csNoDesignVisible in AControl.ControlStyle) or
|
|
not Assigned(Candidates.Find(AControl))
|
|
then
|
|
continue;
|
|
AWinControl := TWinControl(AControl);
|
|
CurTab := AWinControl.TabOrder;
|
|
NodeBehind := FirstSibling;
|
|
while (NodeBehind <> nil) and (TWinControl(NodeBehind.Data).TabOrder <= CurTab) do
|
|
NodeBehind := NodeBehind.GetNextSibling;
|
|
NodeText := AWinControl.Name + ' (' + IntToStr(AWinControl.TabOrder) + ')';
|
|
if NodeBehind <> nil then
|
|
NewNode := ItemTreeview.Items.InsertObject(NodeBehind, NodeText, AControl)
|
|
else
|
|
NewNode := ItemTreeview.Items.AddChildObject(ParentNode, NodeText, AControl);
|
|
if (FirstSibling = nil) or (NewNode.GetPrevSibling = nil) then
|
|
FirstSibling := NewNode;
|
|
CreateNodes(AWinControl, NewNode, Candidates);
|
|
NewNode.Expanded := True;
|
|
end;
|
|
ItemTreeview.EndUpdate;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.RefreshTree;
|
|
var
|
|
LookupRoot: TPersistent;
|
|
Candidates: TAvlTree;
|
|
begin
|
|
if not IsVisible then Exit;
|
|
ItemTreeview.BeginUpdate;
|
|
try
|
|
ItemTreeview.Items.Clear;
|
|
LookupRoot := GlobalDesignHook.LookupRoot;
|
|
if LookupRoot is TWinControl then begin
|
|
Candidates := TAvlTree.Create;
|
|
try
|
|
CreateCandidates(TComponent(LookupRoot), Candidates);
|
|
CreateNodes(TWinControl(LookupRoot), nil, Candidates);
|
|
finally
|
|
Candidates.Free;
|
|
end;
|
|
Caption := Format(lisTabOrderOf, [TWinControl(LookupRoot).Name]);
|
|
end else
|
|
Caption := lisMenuViewTabOrder;
|
|
finally
|
|
ItemTreeview.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.SomethingChanged;
|
|
begin
|
|
if FUpdating then Exit;
|
|
FUpdating := true;
|
|
RefreshTree;
|
|
CheckButtonsEnabled;
|
|
FUpdating := false;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.ComponentRenamed(AComponent: TComponent);
|
|
begin
|
|
SomethingChanged;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.PersistentAdded(APersistent: TPersistent; Select: boolean);
|
|
begin
|
|
SomethingChanged;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.PersistentDeleting(APersistent: TPersistent);
|
|
begin
|
|
SomethingChanged;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.DeletePersistent(var APersistent: TPersistent);
|
|
begin
|
|
ShowMessage('TTabOrderDialog.OnDeletePersistent is never called for some reason!');
|
|
SomethingChanged;
|
|
end;
|
|
|
|
procedure TTabOrderDialog.SetSelection(const ASelection: TPersistentSelectionList);
|
|
// Select item also in TreeView when selection in Designer changes.
|
|
|
|
function FindSelection: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Node := ItemTreeview.Items.GetFirstNode;
|
|
while Assigned(Node) do
|
|
begin
|
|
if Assigned(Node.Data)
|
|
and (ASelection.IndexOf(TPersistent(Node.Data)) >= 0) then
|
|
Exit(Node);
|
|
Node := Node.GetNext;
|
|
end;
|
|
Result := Nil;
|
|
end;
|
|
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
// ToDo: support also multiply selections.
|
|
if FUpdating then Exit;
|
|
FUpdating := True;
|
|
Node := FindSelection;
|
|
if Node = Nil then
|
|
begin // Changing TabSheet in a PageControl does not trigger handlers
|
|
RefreshTree; // thus selection is not found -> refresh tree and try again.
|
|
Node := FindSelection;
|
|
end;
|
|
if Assigned(Node) then
|
|
ItemTreeview.Selected := Node;
|
|
FUpdating := False;
|
|
CheckButtonsEnabled;
|
|
end;
|
|
|
|
{ TTabOrderPropEditor }
|
|
|
|
function TTabOrderPropEditor.GetAttributes: TPropertyAttributes;
|
|
begin
|
|
Result:=(inherited GetAttributes)+[paDialog];
|
|
end;
|
|
|
|
procedure TTabOrderPropEditor.Edit;
|
|
begin
|
|
ShowTabOrderEditor(Self);
|
|
end;
|
|
|
|
initialization
|
|
RegisterPropertyEditor(TypeInfo(TTabOrder), TControl, 'TabOrder', TTabOrderPropEditor);
|
|
|
|
end.
|
|
|