Mark van Renswoude
9c720f5922
Changed: MapList now uses JSON format, preview filename parameter added Changed: cleaned up preview folder, no more duplicates Changed: version number now uses XE2's auto generated date numbers
405 lines
10 KiB
ObjectPascal
405 lines
10 KiB
ObjectPascal
unit Forms.Map;
|
|
|
|
interface
|
|
uses
|
|
System.Classes,
|
|
System.Generics.Collections,
|
|
Vcl.Controls,
|
|
Vcl.ExtCtrls,
|
|
Vcl.Forms,
|
|
Vcl.Graphics,
|
|
Vcl.StdCtrls,
|
|
|
|
VirtualTrees,
|
|
|
|
Frame.MapPreview,
|
|
Game.Base,
|
|
Game.Intf;
|
|
|
|
|
|
type
|
|
TMapForm = class(TForm)
|
|
btnCancel: TButton;
|
|
btnOK: TButton;
|
|
edtMapName: TEdit;
|
|
lblMapName: TLabel;
|
|
pnlButtons: TPanel;
|
|
pnlMapName: TPanel;
|
|
vstMap: TVirtualStringTree;
|
|
pnlFilter: TPanel;
|
|
lblFilter: TLabel;
|
|
edtFilter: TEdit;
|
|
frmMapPreview: TMapPreviewFrame;
|
|
pnlMapPreview: TPanel;
|
|
|
|
procedure btnOKClick(Sender: TObject);
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure vstMapNodeDblClick(Sender: TBaseVirtualTree; const HitInfo: THitInfo);
|
|
procedure vstMapGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
|
|
procedure vstMapPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
|
|
procedure vstMapFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
|
|
procedure vstMapFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
procedure vstMapChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
procedure vstMapCollapsing(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean);
|
|
procedure vstMapCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
|
|
procedure edtMapNameChange(Sender: TObject);
|
|
procedure edtFilterChange(Sender: TObject);
|
|
private
|
|
FLockMapChange: Boolean;
|
|
|
|
function GetMapName: string;
|
|
procedure SetMapName(const Value: string);
|
|
protected
|
|
procedure LoadPredefinedMapList(AGame: IGameMapList);
|
|
procedure CreateMaps(AMaps: TList<TGameMap>);
|
|
|
|
function FindMapNode(const AMapName: string): PVirtualNode;
|
|
|
|
property MapName: string read GetMapName write SetMapName;
|
|
public
|
|
class function Insert(AOwner: TComponent; AGame: IGameMapList; AMaps: TList<TGameMap>): Boolean;
|
|
end;
|
|
|
|
|
|
implementation
|
|
uses
|
|
System.StrUtils,
|
|
System.SysUtils,
|
|
Winapi.Windows;
|
|
|
|
|
|
type
|
|
PGameMap = ^TGameMap;
|
|
|
|
|
|
const
|
|
EmptyCategory = 'Other';
|
|
|
|
|
|
{$R *.dfm}
|
|
|
|
|
|
{ TMapForm }
|
|
class function TMapForm.Insert(AOwner: TComponent; AGame: IGameMapList; AMaps: TList<TGameMap>): Boolean;
|
|
begin
|
|
with Self.Create(AOwner) do
|
|
try
|
|
LoadPredefinedMapList(AGame);
|
|
|
|
Result := (ShowModal = mrOk);
|
|
if Result then
|
|
CreateMaps(AMaps);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.FormCreate(Sender: TObject);
|
|
begin
|
|
vstMap.NodeDataSize := SizeOf(PGameMap);
|
|
end;
|
|
|
|
|
|
procedure TMapForm.LoadPredefinedMapList(AGame: IGameMapList);
|
|
var
|
|
map: TGameMap;
|
|
categoryNodes: TDictionary<string, PVirtualNode>;
|
|
parentNode: PVirtualNode;
|
|
node: PVirtualNode;
|
|
|
|
begin
|
|
vstMap.BeginUpdate;
|
|
try
|
|
vstMap.Clear;
|
|
|
|
categoryNodes := TDictionary<string, PVirtualNode>.Create;
|
|
try
|
|
for map in AGame.PredefinedMapList do
|
|
begin
|
|
if categoryNodes.ContainsKey(map.Category) then
|
|
parentNode := categoryNodes[map.Category]
|
|
else
|
|
begin
|
|
parentNode := vstMap.AddChild(nil, map);
|
|
categoryNodes.Add(map.Category, parentNode);
|
|
end;
|
|
|
|
vstMap.AddChild(parentNode, map);
|
|
end;
|
|
finally
|
|
FreeAndNil(categoryNodes);
|
|
end;
|
|
finally
|
|
vstMap.FullExpand;
|
|
vstMap.EndUpdate;
|
|
|
|
node := vstMap.GetFirstLevel(1);
|
|
if Assigned(node) then
|
|
begin
|
|
vstMap.FocusedNode := node;
|
|
vstMap.Selected[node] := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.CreateMaps(AMaps: TList<TGameMap>);
|
|
var
|
|
node: PVirtualNode;
|
|
nodeData: PGameMap;
|
|
|
|
begin
|
|
if vstMap.SelectedCount > 1 then
|
|
begin
|
|
for node in vstMap.SelectedNodes do
|
|
begin
|
|
if vstMap.GetNodeLevel(node) = 1 then
|
|
begin
|
|
nodeData := vstMap.GetNodeData(node);
|
|
AMaps.Add(TGameMap.Create(nodeData^));
|
|
end;
|
|
end;
|
|
end else
|
|
begin
|
|
node := FindMapNode(MapName);
|
|
if Assigned(node) then
|
|
begin
|
|
nodeData := vstMap.GetNodeData(node);
|
|
AMaps.Add(TGameMap.Create(nodeData^));
|
|
end else
|
|
AMaps.Add(TGameMap.Create(MapName, '', ''));
|
|
end;
|
|
end;
|
|
|
|
|
|
function TMapForm.GetMapName: string;
|
|
begin
|
|
Result := Trim(edtMapName.Text);
|
|
end;
|
|
|
|
|
|
procedure TMapForm.SetMapName(const Value: string);
|
|
begin
|
|
edtMapName.Text := Value;
|
|
end;
|
|
|
|
|
|
function TMapForm.FindMapNode(const AMapName: string): PVirtualNode;
|
|
begin
|
|
Result := vstMap.IterateSubtree(nil,
|
|
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
|
|
var
|
|
nodeData: PGameMap;
|
|
|
|
begin
|
|
nodeData := Sender.GetNodeData(Node);
|
|
Abort := (Sender.GetNodeLevel(Node) > 0) and SameText(nodeData^.Name, AMapName);
|
|
end,
|
|
nil);
|
|
end;
|
|
|
|
|
|
procedure TMapForm.btnOKClick(Sender: TObject);
|
|
begin
|
|
if (vstMap.SelectedCount <= 1) and (Length(MapName) = 0) then
|
|
begin
|
|
MessageBox(Self.Handle, 'Please enter a map name', 'Error', MB_OK or MB_ICONERROR);
|
|
ActiveControl := edtMapName;
|
|
exit;
|
|
end;
|
|
|
|
ModalResult := mrOk;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapNodeDblClick(Sender: TBaseVirtualTree; const HitInfo: THitInfo);
|
|
begin
|
|
btnOK.Click;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
|
|
var
|
|
nodeData: PGameMap;
|
|
|
|
begin
|
|
nodeData := Sender.GetNodeData(Node);
|
|
|
|
if Sender.GetNodeLevel(Node) = 0 then
|
|
begin
|
|
if Length(nodeData^.Category) > 0 then
|
|
CellText := nodeData^.Category
|
|
else
|
|
CellText := EmptyCategory;
|
|
end else
|
|
CellText := nodeData^.DisplayName;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
|
|
begin
|
|
if (TextType = ttNormal) and (Sender.GetNodeLevel(Node) = 0) then
|
|
TargetCanvas.Font.Style := [fsBold];
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
|
|
var
|
|
sibling: PVirtualNode;
|
|
|
|
begin
|
|
Allowed := True;
|
|
|
|
if not Assigned(NewNode) then
|
|
exit;
|
|
|
|
{ Prevent selection of categories while being keyboard-friendly }
|
|
if Sender.GetNodeLevel(NewNode) = 0 then
|
|
begin
|
|
if Assigned(OldNode) then
|
|
begin
|
|
if Sender.AbsoluteIndex(OldNode) < Sender.AbsoluteIndex(NewNode) then
|
|
begin
|
|
{ Moving forwards }
|
|
Sender.FocusedNode := Sender.GetFirstChild(NewNode);
|
|
end else
|
|
begin
|
|
{ Moving backwards }
|
|
sibling := Sender.GetPreviousSibling(NewNode);
|
|
if Assigned(sibling) then
|
|
Sender.FocusedNode := Sender.GetLastChild(sibling);
|
|
end;
|
|
end;
|
|
|
|
Allowed := False;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
|
var
|
|
nodeData: PGameMap;
|
|
|
|
begin
|
|
if FLockMapChange then
|
|
exit;
|
|
|
|
if Assigned(Node) then
|
|
begin
|
|
nodeData := Sender.GetNodeData(Node);
|
|
frmMapPreview.Load(nodeData^.PreviewName);
|
|
|
|
FLockMapChange := True;
|
|
try
|
|
edtMapName.Text := nodeData^.Name;
|
|
finally
|
|
FLockMapChange := False;
|
|
end;
|
|
end else
|
|
frmMapPreview.Clear;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
const
|
|
EditColor: array[Boolean] of TColor = (clBtnFace, clWindow);
|
|
|
|
begin
|
|
edtMapName.Enabled := (Sender.selectedCount <= 1);
|
|
edtMapName.Color := EditColor[edtMapName.Enabled];
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapCollapsing(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean);
|
|
begin
|
|
Allowed := False;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.vstMapCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
|
|
var
|
|
nodeData1: PGameMap;
|
|
nodeData2: PGameMap;
|
|
|
|
begin
|
|
nodeData1 := Sender.GetNodeData(Node1);
|
|
nodeData2 := Sender.GetNodeData(Node2);
|
|
|
|
if Sender.GetNodeLevel(Node1) = 0 then
|
|
Result := CompareText(nodeData1^.Category, nodeData2^.Category)
|
|
else
|
|
Result := CompareText(nodeData1^.DisplayName, nodeData2^.DisplayName);
|
|
end;
|
|
|
|
|
|
procedure TMapForm.edtFilterChange(Sender: TObject);
|
|
var
|
|
node: PVirtualNode;
|
|
|
|
begin
|
|
vstMap.BeginUpdate;
|
|
try
|
|
{ First run; set visibility of map nodes }
|
|
vstMap.IterateSubtree(nil,
|
|
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
|
|
var
|
|
nodeData: PGameMap;
|
|
|
|
begin
|
|
if Sender.GetNodeLevel(Node) > 0 then
|
|
begin
|
|
nodeData := Sender.GetNodeData(Node);
|
|
Sender.IsFiltered[Node] := (Length(edtFilter.Text) > 0) and (not ContainsText(nodeData^.DisplayName, edtFilter.Text));
|
|
end;
|
|
end,
|
|
nil);
|
|
|
|
{ Second run; hide empty categories }
|
|
node := vstMap.GetFirst;
|
|
while Assigned(node) do
|
|
begin
|
|
vstMap.IsFiltered[Node] := not Assigned(vstMap.IterateSubtree(node,
|
|
procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
|
|
begin
|
|
Abort := not Sender.IsFiltered[Node];
|
|
end,
|
|
nil, [], False, True));
|
|
|
|
node := vstMap.GetNextSibling(node);
|
|
end;
|
|
finally
|
|
vstMap.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMapForm.edtMapNameChange(Sender: TObject);
|
|
var
|
|
node: PVirtualNode;
|
|
|
|
begin
|
|
if FLockMapChange then
|
|
exit;
|
|
|
|
node := FindMapNode(MapName);
|
|
FLockMapChange := True;
|
|
try
|
|
vstMap.FocusedNode := node;
|
|
finally
|
|
FLockMapChange := False;
|
|
end;
|
|
|
|
if Assigned(node) then
|
|
begin
|
|
vstMap.Selected[node] := True;
|
|
frmMapPreview.Load(MapName);
|
|
end else
|
|
begin
|
|
vstMap.ClearSelection;
|
|
frmMapPreview.Clear;
|
|
end;
|
|
end;
|
|
|
|
end.
|